すべての標準コンテナには、そのコンテナのイテレータを返すためのbegin
およびend
メソッドがあります。ただし、C++ 11には std::begin
および std::end
begin
およびend
メンバー関数を呼び出します。だから、書く代わりに
auto i = v.begin();
auto e = v.end();
あなたは書くだろう
using std::begin;
using std::end;
auto i = begin(v);
auto e = end(v);
Writing Modern C++ の講演で、Herb Sutter氏は、コンテナのイテレータの開始または終了が必要な場合は、今すぐ無料の関数を使用する必要があると述べています。ただし、彼はwhyについて詳しく説明していません。コードを見ると、1文字すべてを節約できます。そのため、標準のコンテナに関する限り、無料の機能はまったく役に立たないようです。ハーブ・サッターは、非標準の容器には利点があると指摘しましたが、詳細については詳しく説明しませんでした。
したがって、質問は、std::begin
およびstd::end
は、対応するメンバー関数バージョンを呼び出すだけではありません。なぜそれらを使用するのでしょうか?
C配列で.begin()
および.end()
を呼び出す方法は?
自由関数を使用すると、変更できないデータ構造に後で追加できるため、より汎用的なプログラミングが可能になります。
クラスを含むライブラリがある場合を考えてみましょう。
_class SpecialArray;
_
2つのメソッドがあります。
_int SpecialArray::arraySize();
int SpecialArray::valueAt(int);
_
値を反復するには、このクラスから継承し、begin()
およびend()
メソッドを定義する必要があります。
_auto i = v.begin();
auto e = v.end();
_
しかし、常に使用する場合
_auto i = begin(v);
auto e = end(v);
_
あなたはこれを行うことができます:
_template <>
SpecialArrayIterator begin(SpecialArray & arr)
{
return SpecialArrayIterator(&arr, 0);
}
template <>
SpecialArrayIterator end(SpecialArray & arr)
{
return SpecialArrayIterator(&arr, arr.arraySize());
}
_
ここで、SpecialArrayIterator
は次のようなものです。
_class SpecialArrayIterator
{
SpecialArrayIterator(SpecialArray * p, int i)
:index(i), parray(p)
{
}
SpecialArrayIterator operator ++();
SpecialArrayIterator operator --();
SpecialArrayIterator operator ++(int);
SpecialArrayIterator operator --(int);
int operator *()
{
return parray->valueAt(index);
}
bool operator ==(SpecialArray &);
// etc
private:
SpecialArray *parray;
int index;
// etc
};
_
i
とe
は、SpecialArrayの値の反復とアクセスに合法的に使用できるようになりました。
begin
およびend
free関数を使用すると、1つの間接層が追加されます。通常、これは柔軟性を高めるために行われます。
この場合、いくつかの用途を考えることができます。
最も明白な使用法は、C配列(cポインターではありません)です。
もう1つは、非準拠コンテナで標準アルゴリズムを使用しようとした場合です(つまり、コンテナに.begin()
メソッドがありません)。コンテナを修正することはできないと仮定すると、次に最適なオプションはbegin
関数をオーバーロードすることです。ハーブは、コードの均一性と一貫性を促進するために、常にbegin
関数を使用することを提案しています。メソッドbegin
をサポートしているコンテナと関数begin
を必要としているコンテナを覚える必要はありません。
余談ですが、次のC++ revでは、Dの擬似メンバー表記をコピーする必要があります。 a.foo(b,c,d)
が定義されていない場合、代わりにfoo(a,b,c,d)
を試行します。それは、主語よりも動詞の順序付けを好む私たちの貧しい人間を助けるためのほんの少しの構文上の砂糖です。
あなたの質問に答えるために、無料の関数begin()およびend()はデフォルトで、コンテナのメンバー.begin()および.end()関数を呼び出すだけです。 <iterator>
、<vector>
などの標準コンテナを使用すると自動的に含まれる<list>
から、次のようになります。
template< class C >
auto begin( C& c ) -> decltype(c.begin());
template< class C >
auto begin( const C& c ) -> decltype(c.begin());
質問の2番目の部分は、とにかくメンバー関数を呼び出すだけであれば、無料関数を好む理由です。これは、サンプルコードに含まれるv
オブジェクトの種類によって異なります。 vの型がvector<T> v;
のような標準のコンテナ型である場合、無料関数とメンバー関数のどちらを使用しても問題ありません。それらは同じことを行います。次のコードのように、オブジェクトv
がより汎用的な場合:
template <class T>
void foo(T& v) {
auto i = v.begin();
auto e = v.end();
for(; i != e; i++) { /* .. do something with i .. */ }
}
次に、メンバー関数を使用すると、T = C配列、C文字列、列挙などのコードが破損します。非メンバー関数を使用すると、簡単に拡張できるより一般的なインターフェイスを宣伝できます。無料の関数インターフェイスを使用して:
template <class T>
void foo(T& v) {
auto i = begin(v);
auto e = end(v);
for(; i != e; i++) { /* .. do something with i .. */ }
}
コードは、T = C配列とC文字列で動作するようになりました。次に、少量のアダプターコードを記述します。
enum class color { RED, GREEN, BLUE };
static color colors[] = { color::RED, color::GREEN, color::BLUE };
color* begin(const color& c) { return begin(colors); }
color* end(const color& c) { return end(colors); }
コードを反復可能な列挙型とも互換性を持たせることができます。ハーブの主なポイントは、無料関数の使用はメンバー関数の使用と同じくらい簡単であり、Cシーケンス型との後方互換性および非stlシーケンス型(およびfuture-stl型!)他の開発者にとっては低コストです。
非メンバ関数は標準コンテナには何の利点も提供しませんが、それらを使用すると、より一貫した柔軟なスタイルが強制されます。ある時点で既存の非stdコンテナクラスを拡張する場合は、既存のクラスの定義を変更するのではなく、空き関数のオーバーロードを定義する方がいいでしょう。したがって、非標準コンテナの場合は非常に便利であり、常に無料関数を使用するとコードがより柔軟になり、非標準コンテナを標準コンテナに簡単に置き換えることができ、基になるコンテナタイプがコードに対してより透過的になりますさまざまなコンテナ実装をサポートしています。
ただし、もちろんこれは常に適切に重み付けする必要があり、抽象化を超えることもよくありません。無料の関数を使用することはそれほど抽象的ではありませんが、それでもC++ 03コードとの互換性を損ないます。
std::begin
およびstd::end
の利点の1つは、外部クラスの標準インターフェイスを実装するための拡張ポイントとして機能することです。
.begin()
および.end()
メソッドを予期する範囲ベースのforループまたはテンプレート関数でCustomContainer
クラスを使用する場合は、明らかにそれらを実装する必要があります。メソッド。
クラスがそれらのメソッドを提供する場合、それは問題ではありません。そうでない場合は、変更する必要があります*。
これは、たとえば外部ライブラリ、特に商用のクローズドソースライブラリを使用する場合など、常に実行可能ではありません。
このような状況では、std::begin
とstd::end
が便利です。クラス自体を変更せずに、フリー関数をオーバーロードすることなくイテレーターAPIを提供できるためです。
例:イテレータのペアの代わりにコンテナを取るcount_if
関数を実装するとします。このようなコードは次のようになります。
template<typename ContainerType, typename PredicateType>
std::size_t count_if(const ContainerType& container, PredicateType&& predicate)
{
using std::begin;
using std::end;
return std::count_if(begin(container), end(container),
std::forward<PredicateType&&>(predicate));
}
これで、このカスタムcount_if
で使用するクラスについては、それらのクラスを変更する代わりに、2つの無料の関数を追加するだけで済みます。
現在、C++には Argument Dependent Lookup (ADL)と呼ばれるメカニズムがあり、これによりこのようなアプローチはさらに柔軟になります。
つまり、ADLは、コンパイラが非修飾関数(つまり、std::begin
ではなくbegin
のような名前空間のない関数)を解決するときに、引数の名前空間で宣言された関数も考慮することを意味します。例えば:
namesapce some_lib
{
// let's assume that CustomContainer stores elements sequentially,
// and has data() and size() methods, but not begin() and end() methods:
class CustomContainer
{
...
};
}
namespace some_lib
{
const Element* begin(const CustomContainer& c)
{
return c.data();
}
const Element* end(const CustomContainer& c)
{
return c.data() + c.size();
}
}
// somewhere else:
CustomContainer c;
std::size_t n = count_if(c, somePredicate);
この場合、修飾名がsome_lib::begin
とsome_lib::end
であるかどうかは関係ありません-CustomContainer
もsome_lib::
にあるため、コンパイラはcount_if
のオーバーロードを使用します。
また、using std::begin;
にusing std::end;
とcount_if
が含まれている理由でもあります。これにより、非修飾のbegin
およびend
を使用できるため、ADLandを許可し、コンパイラーがstd::begin
およびstd::end
を選択できるようにします他の選択肢が見つからない場合。
クッキーを食べてクッキーを手に入れることができます-i。 e。 begin
/end
のカスタム実装を提供する方法がありますが、コンパイラは標準の実装にフォールバックできます。
いくつかのメモ:
同じ理由で、同様の関数が他にもあります:std::rbegin
/rend
、std::size
、std::data
。
他の回答が言及しているように、std::
バージョンには、ネイキッド配列のオーバーロードがあります。これは便利ですが、上記で説明した特殊なケースにすぎません。
テンプレートコードを記述するときは、std::begin
とfriendsを使用することが特に良いアイデアです。これにより、これらのテンプレートがより汎用的になります。テンプレート以外の場合は、必要に応じてメソッドを使用することもできます。
P。 S.この投稿は7年近く前のものです。重複としてマークされた質問に答えたいと思ったので、ここで答えたのはADLに言及していないことがわかったからです。