C ++ 11でメンバー以外の開始関数と終了関数を使用する理由


197

すべての標準コンテナには、そのコンテナのイテレータを返すためのbeginおよびendメソッドがあります。ただし、C ++ 11には、と呼ばれる無料の関数と、およびのメンバー関数を呼び出すことが明らかに導入されstd::beginstd::endいます。だから、書く代わりにbeginend

auto i = v.begin();
auto e = v.end();

あなたは書くでしょう

auto i = std::begin(v);
auto e = std::end(v);

Herb Sutterは、彼の講演「Writing Modern C ++」で、コンテナーの開始イテレーターまたは終了イテレーターが必要な場合は常に無料の関数を使用する必要があると述べています。しかし、なぜあなたがそうしたいのについて彼は詳細には触れません。コードを見ると、1つの文字をすべて節約できます。したがって、標準のコンテナに関する限り、無料の関数はまったく役に立たないようです。Herb Sutterは、非標準のコンテナにはメリットがあると述べましたが、ここでも詳細には触れませんでした。

では、問題は、の無料の関数バージョンが正確に何でstd::beginありstd::end、対応するメンバー関数のバージョンを呼び出す以外に何をするのか、そしてなぜそれらを使用したいのでしょうか?


29
それは1文字少ないので、子供たちのためにそれらのドットを保存してください:xkcd.com/297
HostileForkはSEを信頼しないと言い

何度std::も繰り返さなければならないので、どういうわけかそれらを使用することを嫌います。
マイケル・チョルダキス

回答:


162

どのように呼んでください.begin().end()C-アレイ上の?

自由関数は、後で変更できないデータ構造に後で追加できるため、より一般的なプログラミングを可能にします。


7
@JonathanMDavis:テンプレートプログラミングトリックを使用endして、静的に宣言された配列(int foo[5])を使用できます。それがポインタに減衰したら、あなたはもちろん運が悪いです。
Matthieu M.

33
template<typename T, size_t N> T* end(T (&a)[N]) { return a + N; }
Hugh

6
@JonathanMDavis:他の人が示したように、C配列を取得beginendて、自分でポインタに合わせて減衰させていない限り、確かにC配列を取得できます。理由としては、配列を使用してコードをリファクタリングし、ベクトルを使用することを想像してください(または、何らかの理由でその逆)。beginand を使用していて、endおそらく何らかの巧妙なtypedeffing を使用している場合は、実装コードをまったく変更する必要はありません(おそらく一部のtypedefを除く)。
Karl Knechtel、2009

31
@JonathanMDavis:配列はポインタではありません。そして誰にとっても:この絶え間なく目立つ混乱を終わらせるために、(一部の)ポインターを「減衰した配列」と呼ぶのをやめます。言語にはそのような用語はなく、実際に使用することはありません。ポインタはポインタ、配列は配列です。配列は暗黙的に最初の要素へのポインタに変換できますが、これは通常の古いポインタであり、他との区別はありません。もちろん、ケースのクローズ時に、ポインターの「終わり」を取得することはできません。
GManNickG 2011

5
まあ、配列以外にも、コンテナーのような側面を公開するAPIは多数あります。もちろん、サードパーティのAPIを変更することはできませんが、これらの独立した開始/終了関数を簡単に作成できます。
edA-qa mort-ora-y 2011

35

クラスを含むライブラリがある場合を考えてみましょう:

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
};

現在ieSpecialArrayの値の反復とアクセスに合法的に使用できます


8
これにはtemplate<>行を含めないでください。テンプレートを特殊化するのではなく、新しい関数オーバーロードを宣言しています。
David Stone、

33

beginand endfree関数を使用すると、1つの間接層が追加されます。通常、これは柔軟性を高めるために行われます。

この場合、いくつかの使用法を考えることができます。

最も明らかな使用法は、C配列(cポインターではない)の使用です。

もう1つは、非準拠コンテナで標準アルゴリズムを使用しようとする場合です(つまり、コンテナには.begin()メソッドがありません)。コンテナーを単に修正できないと仮定すると、次善の策はbegin関数をオーバーロードすることです。Herbは、常にbegin関数を使用してコードの均一性と一貫性を促進することを推奨しています。メソッドbeginをサポートするコンテナと機能を必要とするコンテナを覚える必要はありませんbegin

余談ですが、次のC ++リビジョンはDの疑似メンバー表記をコピーする必要があります。a.foo(b,c,d)定義されていない場合は、代わりに試みfoo(a,b,c,d)ます。これは、主語よりも動詞の順序を好む貧しい人間を助けるためのちょっとした構文上の砂糖です。


5
疑似部材表記 C#/。Netのように見える拡張メソッド。これらはさまざまな状況で役立ちますが、すべての機能と同様に、「乱用」される傾向があります。
Gareth Wilson

5
疑似メンバー表記は、Intellisenseを使用したコーディングの恩恵です。「a」を押す は、関連する動詞を示し、リストを記憶することから頭脳の力を解放し、関連するAPI関数を発見するのに役立つことで、メンバー以外の関数をクラスに追加する必要なく、機能の重複を防ぐことができます。
マット・カーティス

これをC ++に取り入れるための提案があり、統一関数呼び出し構文(UFCS)という用語を使用しています。
underscore_d

17

あなたの質問に答えるために、デフォルトで無料の関数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); }

コードを反復可能な列挙型と互換性を持つようにすることもできます。Herbの主なポイントは、無料の関数を使用することはメンバー関数を使用するのと同じくらい簡単で、コードにCシーケンス型との下位互換性と非stlシーケンス型(およびfuture-stl型!)との上位互換性があるということです。他の開発者に低コストで。


いい例です。enumただし、私は、参照によって、またはその他の基本的な型をとることはしません。間接的にコピーするよりもコピーの方が安価です。
underscore_d

6

std::beginおよびの1つの利点は、std::end外部クラスの標準インターフェースを実装するための拡張ポイントとして機能することです。

あなたが使用したい場合CustomContainerと、クラスを期待ループまたはテンプレート関数の範囲をベース.begin().end()する方法は、あなたが明らかにそれらのメソッドを実装する必要があると思います。

クラスがこれらのメソッドを提供する場合、それは問題ではありません。そうでない場合は、変更する必要があります*。

これは、たとえば外部ライブラリ、特に商用およびクローズドソースのライブラリを使用する場合など、常に実現可能であるとは限りません。

このような状況で、std::beginかつstd::end1は、クラス自体を変更するのではなく、自由な関数をオーバーロードすることなく、イテレータAPIを提供することができるので、便利になります。

例:count_if 1組のイテレーターの代わりにコンテナーを取る関数を実装するとします。このようなコードは次のようになります。

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は、コンパイラが修飾されていない関数(つまり、beginではなく名前空間のない関数)を解決するときstd::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::beginsome_lib::end するので- CustomContainerであるsome_lib::、あまりにも、コンパイラは、それらの中のオーバーロードを使用しますcount_if

それはまた、ある理由だusing std::begin;using std::end;の中でcount_if。これは、私たちは修飾されていない使用することができますbeginし、endしたがって、ADLを可能にし、 コンパイラが選択できるようにするstd::beginstd::end、他の代替案が見つからないとき。

Cookieを食べてCookieを取得できます。つまり、begin/のカスタム実装を提供する方法があるend一方で、コンパイラは標準のCookieにフォールバックできます。

いくつかのメモ:

  • 同じ理由で、他の同様の関数があります:std::rbegin/ rendstd::sizeおよびstd::data

  • 他の回答が述べているように、std::バージョンにはネイキッド配列のオーバーロードがあります。これは便利ですが、上で説明したことの単なる特別なケースです。

  • std::beginテンプレートコードを記述する場合は、や友人を使用することをお勧めします。これにより、これらのテンプレートがより汎用的になるためです。非テンプレートの場合は、該当する場合はメソッドも使用できます。

PS私はこの投稿が7歳近くであることを知っています。重複としてマークされた質問に答えたくて、ここでADLについて言及している答えがないことに気付いたので、私はそれに遭遇しました。


他の人がやったように想像力に任せるのではなく、特に彼らが実際に
underscore_d

5

非メンバー関数は標準コンテナーに利点を提供しませんが、それらを使用すると、より一貫した柔軟なスタイルが適用されます。ある時点で既存の非stdコンテナクラスを拡張したい場合は、既存のクラスの定義を変更するのではなく、フリー関数のオーバーロードを定義する必要があります。したがって、非stdコンテナーの場合は非常に便利であり、常に無料の関数を使用すると、コードをより柔軟にして、stdコンテナーを非stdコンテナーで簡単に置き換えることができ、基になるコンテナータイプがコードに対してより透過的になります。は、さまざまなコンテナ実装をサポートしています。

しかしもちろん、これは常に適切に重み付けする必要があり、抽象化を超えることも良くありません。無料の関数を使用することはそれほど抽象的ではありませんが、それでも、C ++ 03コードとの互換性が失われます。


3
C ++ 03ではboost::begin()/ だけを使用できるend()ため、実際には非互換性はありません:)
Marc Mutz-mmutz

1
@ MarcMutz-mmutzまあ、依存関係をブーストすることは常にオプションであるとは限りません(にのみ使用する場合はかなりやりすぎですbegin/end)。したがって、純粋なC ++ 03との非互換性も考えます。しかし、前述のように、C ++ 11は(少なくともbegin/end特に)採用がどんどん進んでいるため、互換性はかなり小さくなっています(そして小さくなっています)。
クリスチャン・ラウ

0

最終的には、コンテナにとらわれないように一般化されたコードにメリットがあります。std::vectorコード自体に変更を加えることなく、、配列、または範囲を操作できます。

さらに、コンテナーは、所有されていないコンテナーでさえも、メンバー以外の範囲ベースのアクセサーを使用するコードによって不可知論的に使用できるように改造することができます。

詳細はこちらをご覧ください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.