12

重複の可能性:
非メンバー演算子のオーバーロードはどこに配置する必要がありますか?

std::ostream& operator<<(std::ostream& os, const Foo& foo)SO をブラウジングしているときに、 aまたは aのオーバーロード/定義に関する質問や回答をよく見つけますFoo operator+(const Foo& l, const Foo& r)

これらの演算子をいつどのように作成するか (しないか) は知っていますが、そのnamespaceことについて混乱しています。

次のクラスがある場合:

namespace bar
{
  class Foo {};
}

namespace異なる演算子の定義はどちらに記述すればよいですか?

// Should it be this

namespace bar
{
  std::ostream& operator<<(std::ostream& os, const Foo& foo);
}

// Or this ?

namespace std
{
  ostream& operator<<(ostream& os, const bar::Foo& foo);
}

// Or this ?

std::ostream& operator<<(std::ostream& os, const bar::Foo& foo);

同じ質問が にも当てはまりますoperator+。では、ここでの良い習慣とは何ですか?その理由は何ですか?

4

5 に答える 5

15

ルールは、適切な関数のオーバーロードを探すときに、現在の名前空間と引数の型定義のすべての名前空間の両方が考慮されるということです。これは、引数依存ルックアップ (ADL) と呼ばれます。

したがって、このコードがある場合:

  ::std::ostream& os = /* something */;
  const ::bar::Foo& foo = /* something */;
  os << foo;

次の名前空間が考慮されます。

  • 現在の名前空間
  • ::std、os のタイプがそこで定義されているため
  • ::bar、そこに foo の型が定義されているため

したがって、あなたが挙げた3つの可能性はすべて機能し、一見すると「十分」です。

でも....

::std で新しい関数を定義することは許可されていないため、オーバーロードされた演算子をその名前空間に配置することはできません。(::std でテンプレートを特殊化することは許可されていますが、それはここで行っていることではありません)

次に、「現在の名前空間」が変更される可能性があるため、関数定義をその名前空間に配置すると、常に見つかるとは限りません。

つまり、オーバーロードされた演算子を配置するのに最適な場所は、Foo と同じ名前空間です。

namespace bar   
{   
  std::ostream& operator<<(std::ostream& os, const Foo& foo);   
}   
于 2010-10-08T14:23:17.857 に答える
13

bar名前空間にある必要があります。クラスのインターフェースを構成するものを検討し、それらをグループ化する必要があります。

「クラスは、データのセットと、そのデータを操作する関数を記述します。」無料の関数はで動作するFooため、の一部ですFooFoo名前空間でグループ化する必要がありますbar

引数依存のルックアップ(ADL)は、関数を検索します。

また、非フレンド非メンバー機能を優先する必要があることもわかっています。これが意味することは、一般に、クラスには定義関数とメンバー関数があり、その直後にクラスを操作する無料の関数が続くということです。

于 2010-10-08T14:19:13.760 に答える
2

演算子のオーバーロードが正しく機能するには、関数がそのオペランドの 1 つと同じ名前空間にある必要があります。それ以外の場合、ADL はそれを見つけられません。これは、+ や - などの演算子のクラスの名前空間を意味します。理論的には、 operator<< を std またはクラスと同じ名前空間に配置できますが、標準では新しい関数を std で定義することは禁止されているため、ここでもクラスと同じ名前空間に配置します。

(もちろん、通常は + や - を実装するのではなく、+= と -= を実装し、+ と - を自動的に提供するテンプレートから派生させます。)

于 2010-10-08T15:06:49.757 に答える
0

最良の選択はオプション1です。なぜですか?修飾されていない関数名を使用すると(オーバーロードされた演算子は関数です)、通常の名前ルックアップとは別に、引数依存ルックアップが適用されます。つまり、(非公式に)引数が宣言されたすべての名前空間が検索されます。例えば

namespace N
{
   class X(){};
   void f(X){}
}
int main()
{
    N::X x;
    f(x); //works fine, no need to qualify f like N::f
}

演算子についても同じです。

一方、オプション2の場合でも、ostreamはstdにあるため(同じADLルール)、演算子が見つかります。ただし、std名前空間に何かを追加することはお勧めできません。

そして、3番目のオプションはスタイル的に悪いです-最初のオプションで十分なのになぜそれをするのですか?

したがって、間違いなくオプション1です。

HTH。

于 2010-10-08T14:26:39.603 に答える
0

適切な方法は、(非メンバーの) 演算子を、それらが属するインターフェイスを持つクラスと同じ名前空間で宣言することです。

のようなものの場合operator+、これはかなり簡単です。これは Foo オブジェクトでのみ動作するため、Foo 自体と同じ名前空間に移動する必要があります。operator<<との場合、ネームスペースとoperator>>の間で選択できます。まず第一に、関数/演算子のオーバーロードを namespace に追加することは想定されていません。次に、これらのオーバーロードの重要な部分は、ストリームを操作することではなく、Foo オブジェクトを読み書きすることです。したがって、それを Foo クラスにバンドルする方が理にかなっています。stdbarstd

また、C++ の規則は、オーバーロードされた演算子が操作対象のクラスと同じ名前空間で定義されている場合、ほとんどの場合正しく検出されるように設計されていることにも注意してください。他の無関係な名前空間で。

于 2010-10-08T14:30:31.970 に答える