演算子のオーバーロードの基本的なルールとイディオムは何ですか?

2203
sbi 2010-12-13 02:44.

注:回答は特定の順序で与えられましが、多くのユーザーは与えられた時間ではなく投票に従って回答を並べ替えているため、ここに最も意味のある順序での回答のインデックスを示します

  • C ++での演算子オーバーロードの一般的な構文
  • C ++での演算子オーバーロードの3つの基本ルール
  • メンバーと非メンバーの間の決定
  • オーバーロードする一般的な演算子
  • 代入演算子
  • 入出力演算子
  • 関数呼び出し演算子
  • 比較演算子
  • 算術演算子
  • 配列の添え字
  • ポインタのような型の演算子
  • 変換演算子
  • 新規のオーバーロードと削除
_(注:これは、[StackOverflowのC ++ FAQ](https://stackoverflow.com/questions/tagged/c++-faq)へのエントリです。このフォームでFAQを提供するというアイデアを批判したい場合、そして[これをすべて始めたメタへの投稿](https://meta.stackexchange.com/questions/68647/setting-up-a-faq-for-the-c-tag)はそれを行う場所です。その質問への回答は、[C ++チャットルーム](http://chat.stackoverflow.com/rooms/10/c-lounge)で監視されています。ここでは、FAQのアイデアが最初に始まったので、あなたの回答はアイデアを思いついた人に読んでもらいましょう。)_

7 answers

1076
sbi 2010-12-13 02:47.

オーバーロードする一般的な演算子

オーバーロード演算子での作業のほとんどは、ボイラープレートコードです。演算子は単なる構文糖衣であるため、実際の作業は単純な関数によって実行できます(多くの場合、転送されます)。ただし、この定型コードを正しく取得することが重要です。失敗すると、オペレーターのコードがコンパイルされないか、ユーザーのコードがコンパイルされないか、ユーザーのコードが驚くほど動作します。

代入演算子

割り当てについては、言うべきことがたくさんあります。ただし、そのほとんどはGManの有名なコピーアンドスワップFAQですでに説明されているため、ここではほとんどスキップし、参照用に最適な代入演算子のみをリストします。

X& X::operator=(X rhs)
{
  swap(rhs);
  return *this;
}

ビットシフト演算子(ストリームI / Oに使用)

ビットシフト演算子<<とは>>、Cから継承するビット操作関数のハードウェアインターフェイスで引き続き使用されますが、ほとんどのアプリケーションでオーバーロードされたストリーム入出力演算子として普及しています。ビット操作演算子としてのガイダンスのオーバーロードについては、以下の2進算術演算子に関するセクションを参照してください。オブジェクトがiostreamで使用されるときに、独自のカスタム形式と解析ロジックを実装するには、続行します。

最も一般的にオーバーロードされる演算子の中で、ストリーム演算子は、構文がメンバーであるか非メンバーであるかについての制限を指定しないバイナリ中置演算子です。それらは左引数を変更する(ストリームの状態を変更する)ため、経験則に従って、左オペランドの型のメンバーとして実装する必要があります。ただし、それらの左側のオペランドは標準ライブラリからのストリームであり、標準ライブラリによって定義されたストリームの出力および入力演算子のほとんどは実際にはストリームクラスのメンバーとして定義されていますが、独自の型の出力および入力操作を実装すると、標準ライブラリのストリームタイプを変更することはできません。そのため、これらの演算子を独自の型に対して非メンバー関数として実装する必要があります。2つの標準形式は次のとおりです。

std::ostream& operator<<(std::ostream& os, const T& obj)
{
  // write obj to stream

  return os;
}

std::istream& operator>>(std::istream& is, T& obj)
{
  // read obj from stream

  if( /* no valid object of T found in stream */ )
    is.setstate(std::ios::failbit);

  return is;
}

を実装する場合operator>>、ストリームの状態を手動で設定する必要があるのは、読み取り自体が成功した場合のみですが、結果は期待どおりではありません。

関数呼び出し演算子

ファンクターとも呼ばれる関数オブジェクトの作成に使用される関数呼び出し演算子は、メンバー関数として定義する必要があるため、常にthisメンバー関数の暗黙の引数があります。これ以外に、ゼロを含む任意の数の追加引数を取るためにオーバーロードすることができます。

構文の例を次に示します。

class foo {
public:
    // Overloaded call operator
    int operator()(const std::string& y) {
        // ...
    }
};

使用法:

foo f;
int a = f("hello");

C ++標準ライブラリ全体で、関数オブジェクトは常にコピーされます。したがって、独自の関数オブジェクトは安価にコピーできるはずです。関数オブジェクトがコピーにコストのかかるデータを絶対に使用する必要がある場合は、そのデータを別の場所に保存し、関数オブジェクトに参照させることをお勧めします。

比較演算子

バイナリ中置比較演算子は、経験則に従って、非メンバー関数として実装する必要があります1。単項接頭辞の否定!は、(同じ規則に従って)メンバー関数として実装する必要があります。(ただし、通常、オーバーロードすることはお勧めできません。)

標準ライブラリのアルゴリズム(eg std::sort())とタイプ(eg std::map)は、常に存在するoperator<ことだけを期待します。ただし、同じタイプユーザーは、他のすべての演算子も存在することを期待するため、を定義する場合operator<は、演算子のオーバーロードの3番目の基本ルールに従い、他のすべてのブール比較演算子も定義してください。それらを実装するための標準的な方法は次のとおりです。

inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return  operator< (rhs,lhs);}
inline bool operator<=(const X& lhs, const X& rhs){return !operator> (lhs,rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !operator< (lhs,rhs);}

ここで注意すべき重要なことは、これらの演算子のうち実際に何かを行うのは2つだけであり、他の演算子は実際の作業を行うためにこれら2つのいずれかに引数を転送しているだけです。

残りのバイナリブール演算子(||&&)をオーバーロードするための構文は、比較演算子の規則に従います。ただし、これら2の妥当な使用例が見つかる可能性はほとんどありません。

1 すべての経験則と同様に、これを破る理由がある場合もあります。その場合、メンバー関数の場合はになるバイナリ比較演算子の左側のオペランドも*thisである必要があることを忘れないでくださいconst。したがって、メンバー関数として実装された比較演算子には、次のシグネチャが必要です。

bool operator<(const X& rhs) const { /* do actual comparison with *this */ }

const最後に注意してください。)

2 の組み込みバージョンは||&&ショートカットセマンティクスを使用していることに注意してください。ユーザー定義のもの(メソッド呼び出しの構文糖衣構文であるため)は、ショートカットセマンティクスを使用しません。ユーザーは、これらの演算子にショートカットセマンティクスがあることを期待し、それらのコードはそれに依存する可能性があるため、絶対に定義しないことを強くお勧めします。

算術演算子

単項算術演算子

単項インクリメント演算子とデクリメント演算子には、プレフィックスとポストフィックスの両方のフレーバーがあります。どちらかを区別するために、接尾辞のバリアントは追加のダミーint引数を取ります。インクリメントまたはデクリメントをオーバーロードする場合は、必ずプレフィックスバージョンとポストフィックスバージョンの両方を実装してください。これがインクリメントの標準的な実装です。デクリメントは同じルールに従います。

class X {
  X& operator++()
  {
    // do actual increment
    return *this;
  }
  X operator++(int)
  {
    X tmp(*this);
    operator++();
    return tmp;
  }
};

接尾辞のバリアントは接頭辞の観点から実装されていることに注意してください。また、接尾辞は余分なコピーを行うことに注意してください。2

単項マイナスとプラスのオーバーロードはあまり一般的ではなく、おそらく回避するのが最善です。必要に応じて、メンバー関数としてオーバーロードする必要があります。

2 また、接尾辞のバリアントはより多くの作業を行うため、接頭辞のバリアントよりも使用効率が低いことに注意してください。これは、一般的に、接尾辞の増分よりも接頭辞の増分を好む理由です。コンパイラーは通常、組み込み型の接尾辞インクリメントの追加作業を最適化できますが、ユーザー定義型(リストイテレーターのように無邪気に見えるもの)に対しては同じことを実行できない場合があります。慣れると、組み込み型でない場合はi++覚えるのが非常に難しくなります(さらに、型を変更するときにコードを変更する必要があります)。そのため、常に習慣を身に付けることをお勧めします。接尾辞が明示的に必要でない限り、接頭辞の増分を使用します。++ii

2進算術演算子

2進算術演算子の場合、3番目の基本ルール演算子のオーバーロードに従うことを忘れないでください。指定する場合は+、指定する+=、指定する場合-は省略しない-=など。AndrewKoenigは、複合割り当てを最初に観察したと言われています。演算子は、非複合の対応する演算子のベースとして使用できます。すなわち、オペレータがさ+の点で実現される+=-という点で実現されている-=

私たちの経験則によれば+、そのコンパニオンは非メンバー+=である必要がありますが、左の引数を変更するそれらの複合代入の対応物(など)はメンバーである必要があります。ここにするための例示的なコードである+=+、他の2進算術演算子も同じ方法で実装する必要があります。

class X {
  X& operator+=(const X& rhs)
  {
    // actual addition of rhs to *this
    return *this;
  }
};
inline X operator+(X lhs, const X& rhs)
{
  lhs += rhs;
  return lhs;
}

operator+=参照ごとに結果を返し、結果operator+のコピーを返します。もちろん、参照を返すことは通常、コピーを返すよりも効率的ですが、の場合operator+、コピーを回避する方法はありません。を書くときa + b、あなたは結果が新しい値であると期待します、それがoperator+新しい値を返さなければならない理由です。3また、const参照ではなく、コピーによってoperator+左側のオペランドを取ることに注意してください。この理由は、コピーごとにその議論をとる理由と同じです。operator=

ビット操作演算子~ & | ^ << >>は、算術演算子と同じ方法で実装する必要があります。ただし、(オーバーロード<<>>出力および入力を除いて)これらをオーバーロードするための合理的なユースケースはほとんどありません。

3 繰り返しになりますが、これから得られる教訓a += bは、一般に、より効率的でa + bあり、可能であれば優先されるべきであるということです。

配列の添え字

配列添え字演算子は二項演算子であり、クラスメンバーとして実装する必要があります。これは、キーによるデータ要素へのアクセスを許可するコンテナのようなタイプに使用されます。これらを提供する標準的な形式は次のとおりです。

class X {
        value_type& operator[](index_type idx);
  const value_type& operator[](index_type idx) const;
  // ...
};

クラスのユーザーがによって返されるデータ要素を変更できないようにしたくないoperator[]場合(この場合、非constバリアントを省略できます)を除き、常に演算子の両方のバリアントを提供する必要があります。

value_typeが組み込み型を参照することがわかっている場合、演算子のconstバリアントは、const参照ではなくコピーを返す方が適切です。

class X {
  value_type& operator[](index_type idx);
  value_type  operator[](index_type idx) const;
  // ...
};

ポインタのような型の演算子

独自のイテレータまたはスマートポインタを定義するには、単項プレフィックス逆参照演算子*とバイナリインフィックスポインタメンバーアクセス演算子をオーバーロードする必要があります->

class my_ptr {
        value_type& operator*();
  const value_type& operator*() const;
        value_type* operator->();
  const value_type* operator->() const;
};

これらも、ほとんどの場合、constバージョンとnon-constバージョンの両方が必要になることに注意してください。ため->た場合、オペレータ、value_typeであるclass(又はstruct又はunionタイプ)、他はoperator->()れるまで、再帰的に呼び出されoperator->()、非クラス型の値を返します。

単項アドレス演算子は決してオーバーロードされるべきではありません。

以下のためのoperator->*()参照この質問を。使用されることはめったにないため、過負荷になることはめったにありません。実際、イテレータでさえ過負荷にはなりません。


進んで変換演算子

507
sbi 2010-12-13 02:45.

C ++での演算子オーバーロードの3つの基本ルール

C ++での演算子のオーバーロードに関しては、従う必要のある3つの基本的なルールがあります。そのようなすべての規則と同様に、確かに例外があります。時々人々は彼らから逸脱し、結果は悪いコードではありませんでしたが、そのような正の逸脱はほとんどありません。少なくとも、私が見た100のそのような逸脱のうち99は不当でした。ただし、1000のうち999だった可能性もあります。したがって、次のルールに従うことをお勧めします。

  1. 演算子の意味が明確で議論の余地がない場合は常に、オーバーロードしないでください。 代わりに、適切に選択された名前で関数を提供してください。
    基本的に、演算子をオーバーロードするための最初のそして最も重要なルールは、その中心にあると言っています:それをしないでください。演算子のオーバーロードについて知っておくべきことがたくさんあるので、それは奇妙に思えるかもしれません。そのため、多くの記事、本の章、およびその他のテキストがこれらすべてを扱っています。しかし、この一見明白な証拠にもかかわらず、演算子のオーバーロードが適切であるケースは驚くほど少ないです。その理由は、アプリケーションドメインでの演算子の使用がよく知られており、議論の余地がない限り、実際には演算子のアプリケーションの背後にあるセマンティクスを理解するのは難しいためです。一般に信じられていることとは反対に、これはほとんどありません。

  2. オペレーターのよく知られたセマンティクスに常に固執します。
    C ++は、オーバーロードされた演算子のセマンティクスに制限を課しません。コンパイラは+、右のオペランドから減算する二項演算子を実装するコードを喜んで受け入れます。しかし、そのようなオペレータのユーザーが表現疑うことはないa + b減算するaからをb。もちろん、これは、アプリケーションドメインの演算子のセマンティクスに議論の余地がないことを前提としています。

  3. 関連する一連の操作からすべてを常に提供します。
    演算子は、相互に、および他の操作に関連しています。あなたのタイプがサポートしている場合a + b、ユーザーは電話をかけることもできると期待しますa += b。プレフィックスの増分をサポートしている場合は、同様に機能する++aことが期待a++されます。かどうかを確認できれば、a < b確実に確認できることも期待できa > bます。彼らがあなたのタイプをコピー構築できるなら、彼らは割り当ても同様に機能することを期待しています。


メンバーと非メンバーの間の決定に進みます。

267
sbi 2010-12-13 02:46.

C ++での演算子オーバーロードの一般的な構文

C ++の組み込み型の演算子の意味を変更することはできません。演算子は、ユーザー定義型1に対してのみオーバーロードできます。つまり、オペランドの少なくとも1つはユーザー定義型である必要があります。他のオーバーロードされた関数と同様に、演算子は特定のパラメーターセットに対して1回だけオーバーロードできます。

C ++ですべての演算子をオーバーロードできるわけではありません。オーバーロードできない演算子には、次のものがあります. :: sizeof typeid .*。C++で唯一の三項演算子。?:

C ++でオーバーロードできる演算子には、次のものがあります。

  • 算術演算子:+ - * / %および+= -= *= /= %=(すべてのバイナリ中置); + -(単項接頭辞); ++ --(単項接頭辞と接尾辞)
  • ビット操作:& | ^ << >>および&= |= ^= <<= >>=(すべてのバイナリインフィックス); ~(単項接頭辞)
  • ブール代数:(== != < > <= >= || &&すべてのバイナリ中置); !(単項接頭辞)
  • メモリ管理: new new[] delete delete[]
  • 暗黙の変換演算子
  • その他:(= [] -> ->* ,すべてのバイナリ中置); * &(すべての単項接頭辞)()(関数呼び出し、n項中置)

ただし、これらすべてを過負荷にできるという事実は、そうする必要があるという意味ではありません。演算子のオーバーロードの基本的なルールを参照してください。

C ++では、演算子は特別な名前関数の形でオーバーロードされます。他の関数と同様に、オーバーロードされた演算子は通常、左側のオペランドの型のメンバー関数として、またはメンバー関数として実装できます。どちらかを自由に選択できるか、使用する必要があるかは、いくつかの基準によって異なります。2オブジェクトxに適用される単項演算子@3は、として[email protected](x)またはとして呼び出されます[email protected]()@オブジェクトxとに適用されるバイナリ中置演算子は、yas[email protected](x,y)またはasと呼ばれ[email protected](y)ます。4

非メンバー関数として実装されている演算子は、オペランドの型と相性が良い場合があります。

1 「ユーザー定義」という用語は、少し誤解を招く可能性があります。C ++は、組み込み型とユーザー定義型を区別します。前者には、たとえばint、char、doubleなどがあります。後者には、標準ライブラリからのものを含むすべての構造体、クラス、共用体、および列挙型が属しますが、それら自体はユーザーによって定義されていません。

2 これについては、このFAQの後半で説明します。

3 、私はプレースホルダとしてそれを使用する理由であるC ++で有効な演算子ではありません。@

4 C ++の唯一の三項演算子はオーバーロードできず、唯一のn項演算子は常にメンバー関数として実装する必要があります。


C ++での演算子のオーバーロードの3つの基本ルールに進みます。

258
sbi 2010-12-13 02:49.

メンバーと非メンバーの間の決定

二項演算子=(代入)、[](配列サブスクリプション)、->(メンバーアクセス)、およびn項()(関数呼び出し)演算子は、言語の構文で必要になるため、常にメンバー関数として実装する必要があります。

他の演算子は、メンバーまたは非メンバーとして実装できます。ただし、それらの一部は、左側のオペランドを変更できないため、通常は非メンバー関数として実装する必要があります。これらの中で最も目立つのは、入力演算子と出力演算子<<、およびです。これらの>>左側のオペランドは、変更できない標準ライブラリのストリームクラスです。

メンバー関数または非メンバー関数のいずれかとして実装することを選択する必要があるすべての演算子について、次の経験則使用して決定します。

  1. 単項演算子の場合は、メンバー関数として実装します。
  2. If a binary operator treats both operands equally (it leaves them unchanged), implement this operator as a non-member function.
  3. If a binary operator does not treat both of its operands equally (usually it will change its left operand), it might be useful to make it a member function of its left operand’s type, if it has to access the operand's private parts.

Of course, as with all rules of thumb, there are exceptions. If you have a type

enum Month {Jan, Feb, ..., Nov, Dec}

and you want to overload the increment and decrement operators for it, you cannot do this as a member functions, since in C++, enum types cannot have member functions. So you have to overload it as a free function. And operator<() for a class template nested within a class template is much easier to write and read when done as a member function inline in the class definition. But these are indeed rare exceptions.

(However, if you make an exception, do not forget the issue of const-ness for the operand that, for member functions, becomes the implicit this argument. If the operator as a non-member function would take its left-most argument as a const reference, the same operator as a member function needs to have a const at the end to make *this a const reference.)


Continue to Common operators to overload.

166
JKor 2013-05-18 08:32.

Conversion Operators (also known as User Defined Conversions)

In C++ you can create conversion operators, operators that allow the compiler to convert between your types and other defined types. There are two types of conversion operators, implicit and explicit ones.

Implicit Conversion Operators (C++98/C++03 and C++11)

An implicit conversion operator allows the compiler to implicitly convert (like the conversion between int and long) the value of a user-defined type to some other type.

The following is a simple class with an implicit conversion operator:

class my_string {
public:
  operator const char*() const {return data_;} // This is the conversion operator
private:
  const char* data_;
};

Implicit conversion operators, like one-argument constructors, are user-defined conversions. Compilers will grant one user-defined conversion when trying to match a call to an overloaded function.

void f(const char*);

my_string str;
f(str); // same as f( str.operator const char*() )

At first this seems very helpful, but the problem with this is that the implicit conversion even kicks in when it isn’t expected to. In the following code, void f(const char*) will be called because my_string() is not an lvalue, so the first does not match:

void f(my_string&);
void f(const char*);

f(my_string());

Beginners easily get this wrong and even experienced C++ programmers are sometimes surprised because the compiler picks an overload they didn’t suspect. These problems can be mitigated by explicit conversion operators.

Explicit Conversion Operators (C++11)

Unlike implicit conversion operators, explicit conversion operators will never kick in when you don't expect them to. The following is a simple class with an explicit conversion operator:

class my_string {
public:
  explicit operator const char*() const {return data_;}
private:
  const char* data_;
};

Notice the explicit. Now when you try to execute the unexpected code from the implicit conversion operators, you get a compiler error:

prog.cpp: In function ‘int main()’:
prog.cpp:15:18: error: no matching function for call to ‘f(my_string)’
prog.cpp:15:18: note: candidates are:
prog.cpp:11:10: note: void f(my_string&)
prog.cpp:11:10: note:   no known conversion for argument 1 from ‘my_string’ to ‘my_string&’
prog.cpp:12:10: note: void f(const char*)
prog.cpp:12:10: note:   no known conversion for argument 1 from ‘my_string’ to ‘const char*’

To invoke the explicit cast operator, you have to use static_cast, a C-style cast, or a constructor style cast ( i.e. T(value) ).

However, there is one exception to this: The compiler is allowed to implicitly convert to bool. In addition, the compiler is not allowed to do another implicit conversion after it converts to bool (a compiler is allowed to do 2 implicit conversions at a time, but only 1 user-defined conversion at max).

Because the compiler will not cast "past" bool, explicit conversion operators now remove the need for the Safe Bool idiom. For example, smart pointers before C++11 used the Safe Bool idiom to prevent conversions to integral types. In C++11, the smart pointers use an explicit operator instead because the compiler is not allowed to implicitly convert to an integral type after it explicitly converted a type to bool.

Continue to Overloading new and delete.

150
sbi 2010-12-13 03:07.

Overloading new and delete

Note: This only deals with the syntax of overloading new and delete, not with the implementation of such overloaded operators. I think that the semantics of overloading new and delete deserve their own FAQ, within the topic of operator overloading I can never do it justice.

Basics

In C++, when you write a new expression like new T(arg) two things happen when this expression is evaluated: First operator new is invoked to obtain raw memory, and then the appropriate constructor of T is invoked to turn this raw memory into a valid object. Likewise, when you delete an object, first its destructor is called, and then the memory is returned to operator delete.
C++ allows you to tune both of these operations: memory management and the construction/destruction of the object at the allocated memory. The latter is done by writing constructors and destructors for a class. Fine-tuning memory management is done by writing your own operator new and operator delete.

The first of the basic rules of operator overloading – don’t do it – applies especially to overloading new and delete. Almost the only reasons to overload these operators are performance problems and memory constraints, and in many cases, other actions, like changes to the algorithms used, will provide a much higher cost/gain ratio than attempting to tweak memory management.

The C++ standard library comes with a set of predefined new and delete operators. The most important ones are these:

void* operator new(std::size_t) throw(std::bad_alloc); 
void  operator delete(void*) throw(); 
void* operator new[](std::size_t) throw(std::bad_alloc); 
void  operator delete[](void*) throw(); 

The first two allocate/deallocate memory for an object, the latter two for an array of objects. If you provide your own versions of these, they will not overload, but replace the ones from the standard library.
If you overload operator new, you should always also overload the matching operator delete, even if you never intend to call it. The reason is that, if a constructor throws during the evaluation of a new expression, the run-time system will return the memory to the operator delete matching the operator new that was called to allocate the memory to create the object in. If you do not provide a matching operator delete, the default one is called, which is almost always wrong.
If you overload new and delete, you should consider overloading the array variants, too.

Placement new

C++ allows new and delete operators to take additional arguments.
So-called placement new allows you to create an object at a certain address which is passed to:

class X { /* ... */ };
char buffer[ sizeof(X) ];
void f()
{ 
  X* p = new(buffer) X(/*...*/);
  // ... 
  p->~X(); // call destructor 
} 

The standard library comes with the appropriate overloads of the new and delete operators for this:

void* operator new(std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete(void* p,void*) throw(); 
void* operator new[](std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete[](void* p,void*) throw(); 

Note that, in the example code for placement new given above, operator delete is never called, unless the constructor of X throws an exception.

You can also overload new and delete with other arguments. As with the additional argument for placement new, these arguments are also listed within parentheses after the keyword new. Merely for historical reasons, such variants are often also called placement new, even if their arguments are not for placing an object at a specific address.

Class-specific new and delete

Most commonly you will want to fine-tune memory management because measurement has shown that instances of a specific class, or of a group of related classes, are created and destroyed often and that the default memory management of the run-time system, tuned for general performance, deals inefficiently in this specific case. To improve this, you can overload new and delete for a specific class:

class my_class { 
  public: 
    // ... 
    void* operator new();
    void  operator delete(void*,std::size_t);
    void* operator new[](size_t);
    void  operator delete[](void*,std::size_t);
    // ... 
}; 

Overloaded thus, new and delete behave like static member functions. For objects of my_class, the std::size_t argument will always be sizeof(my_class). However, these operators are also called for dynamically allocated objects of derived classes, in which case it might be greater than that.

Global new and delete

To overload the global new and delete, simply replace the pre-defined operators of the standard library with our own. However, this rarely ever needs to be done.

47
R Sahu 2016-01-23 09:00.

Why can't operator<< function for streaming objects to std::cout or to a file be a member function?

Let's say you have:

struct Foo
{
   int a;
   double b;

   std::ostream& operator<<(std::ostream& out) const
   {
      return out << a << " " << b;
   }
};

Given that, you cannot use:

Foo f = {10, 20.0};
std::cout << f;

Since operator<< is overloaded as a member function of Foo, the LHS of the operator must be a Foo object. Which means, you will be required to use:

Foo f = {10, 20.0};
f << std::cout

which is very non-intuitive.

If you define it as a non-member function,

struct Foo
{
   int a;
   double b;
};

std::ostream& operator<<(std::ostream& out, Foo const& f)
{
   return out << f.a << " " << f.b;
}

You will be able to use:

Foo f = {10, 20.0};
std::cout << f;

which is very intuitive.

Related questions

MORE COOL STUFF

「水曜日」シーズン1の中心には大きなミステリーがあります

「水曜日」シーズン1の中心には大きなミステリーがあります

Netflixの「水曜日」は、典型的な10代のドラマ以上のものであり、実際、シーズン1にはその中心に大きなミステリーがあります.

ボディーランゲージの専門家は、州訪問中にカミラ・パーカー・ボウルズが輝くことを可能にした微妙なケイト・ミドルトンの動きを指摘しています

ボディーランゲージの専門家は、州訪問中にカミラ・パーカー・ボウルズが輝くことを可能にした微妙なケイト・ミドルトンの動きを指摘しています

ケイト・ミドルトンは、州の夕食会と州の訪問中にカミラ・パーカー・ボウルズからスポットライトを奪いたくなかった、と専門家は言う.

一部のファンがハリー・スタイルズとオリビア・ワイルドの「非常に友好的な」休憩が永続的であることを望んでいる理由

一部のファンがハリー・スタイルズとオリビア・ワイルドの「非常に友好的な」休憩が永続的であることを望んでいる理由

一部のファンが、オリビア・ワイルドが彼女とハリー・スタイルズとの間の「難しい」が「非常に友好的」な分割を恒久的にすることを望んでいる理由を見つけてください.

エリザベス女王の死後、ケイト・ミドルトンはまだ「非常に困難な時期」を過ごしている、と王室の専門家が明らかにする 

エリザベス女王の死後、ケイト・ミドルトンはまだ「非常に困難な時期」を過ごしている、と王室の専門家が明らかにする&nbsp;

エリザベス女王の死後、ケイト・ミドルトンが舞台裏で「非常に困難な時期」を過ごしていたと伝えられている理由を調べてください.

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セント ヘレナ島のジェイコブズ ラダーは 699 段の真っ直ぐ上る階段で、頂上に到達すると証明書が発行されるほどの難易度です。

The Secrets of Airline Travel Quiz

The Secrets of Airline Travel Quiz

Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

バイオニック読書はあなたをより速く読むことができますか?

バイオニック読書はあなたをより速く読むことができますか?

BionicReadingアプリの人気が爆発的に高まっています。しかし、それは本当にあなたを速読術にすることができますか?

Total War:Warhammer:Kotakuレビュー

Total War:Warhammer:Kotakuレビュー

私はこのゲームを嫌う準備ができていました。先週の前に、Total War:Warhammerについての私の考えがありました:それでもここに私は、私の手にある完成品であり、私は変わった男です。

涙の道:軍事化された帝国主義勢力がスタンディングロックキャンプを占領

涙の道:軍事化された帝国主義勢力がスタンディングロックキャンプを占領

スタンディングロックスー族のメンバーと水の保護者は、ノースダコタ州のスタンディングロックにあるオセティサコウィンキャンプを去ります。(Twitter経由のCNNスクリーンショット)火と煙がスカイラインを覆い、スタンディングロックスー族のメンバーと水の保護者が、聖なるものを守りながら建てた家、オセティサコウィン(セブンカウンシルファイアーズ)キャンプから行進し、太鼓を打ち、歌い、祈りました。ダコタアクセスパイプラインとしても知られる「ブラックスネーク」からの土地。

シアーズとKマートはイヴァンカ・トランプの商品を自分たちで取り除いています

シアーズとKマートはイヴァンカ・トランプの商品を自分たちで取り除いています

写真:APシアーズとKマートは、イヴァンカ・トランプのトランプホームアイテムのコレクションも、誰も購入したくないために削除しました。シアーズとKマートの両方の親会社であるシアーズホールディングスは、土曜日のABCニュースへの声明で、彼らが気にかけていると辛抱強く説明しましたトランプラインを売り続けるにはお金を稼ぐことについてあまりにも多く。

ポテトチップスでたった10分でスペインのトルティーヤを作る

ポテトチップスでたった10分でスペインのトルティーヤを作る

伝統的なスペインのトルティーヤは通常、オリーブオイルで柔らかくなるまで調理されたポテトから始まります(30分以上かかる場合があります)が、ケトルで調理されたポテトチップスの助けを借りてわずか10分でテーブルに置くことができます。上のビデオはすべてがバラバラにならないように裏返す方法を含め、レシピ全体を説明しますが、必要なのは4〜5個の卵と3カップのケトルチップスだけです。

ケイト・ミドルトンとウィリアム王子は、彼らが子供たちと行っているスパイをテーマにした活動を共有しています

ケイト・ミドルトンとウィリアム王子は、彼らが子供たちと行っているスパイをテーマにした活動を共有しています

ケイト・ミドルトンとウィリアム王子は、子供向けのパズルの本の序文を書き、ジョージ王子、シャーロット王女、ルイ王子と一緒にテキストを読むと述べた.

事故で押しつぶされたスイカは、動物を喜ばせ水分補給するために野生生物保護団体に寄付されました

事故で押しつぶされたスイカは、動物を喜ばせ水分補給するために野生生物保護団体に寄付されました

Yak's Produce は、数十個のつぶれたメロンを野生動物のリハビリ専門家であるレスリー グリーンと彼女のルイジアナ州の救助施設で暮らす 42 匹の動物に寄付しました。

デミ・ロヴァートは、新しいミュージシャンのボーイフレンドと「幸せで健康的な関係」にあります: ソース

デミ・ロヴァートは、新しいミュージシャンのボーイフレンドと「幸せで健康的な関係」にあります: ソース

8 枚目のスタジオ アルバムのリリースに向けて準備を進めているデミ ロヴァートは、「スーパー グレート ガイ」と付き合っている、と情報筋は PEOPLE に確認しています。

Plathville の Kim と Olivia Plath が数年ぶりに言葉を交わすことへようこそ

Plathville の Kim と Olivia Plath が数年ぶりに言葉を交わすことへようこそ

イーサン プラスの誕生日のお祝いは、TLC のウェルカム トゥ プラスビルのシーズン 4 のフィナーレで、戦争中の母親のキム プラスと妻のオリビア プラスを結びつけました。

仕事の生産性を高める 8 つのシンプルなホーム オフィスのセットアップのアイデア

仕事の生産性を高める 8 つのシンプルなホーム オフィスのセットアップのアイデア

ホームオフィスのセットアップ術を極めよう!AppExert の開発者は、家族全員が一緒にいる場合でも、在宅勤務の技術を習得しています。祖父や曽祖父が共同家族で暮らしていた頃の記憶がよみがえりました。

2022 年、私たちのデジタル ライフはどこで終わり、「リアル ライフ」はどこから始まるのでしょうか?

20 年前のタイムトラベラーでさえ、日常生活におけるデジタルおよびインターネットベースのサービスの重要性に驚くことでしょう。MySpace、eBay、Napster などのプラットフォームは、高速化に焦点を合わせた世界がどのようなものになるかを示してくれました。

ニューロマーケティングの秘密科学

ニューロマーケティングの秘密科学

マーケティング担当者が人間の欲望を操作するために使用する、最先端の (気味が悪いと言う人もいます) メソッドを探ります。カートをいっぱいにして 3 桁の領収書を持って店を出る前に、ほんの数点の商品を買いに行ったことはありませんか? あなたは一人じゃない。

地理情報システムの日: GIS 開発者として学ぶべき最高の技術スタック

地理情報システムの日: GIS 開発者として学ぶべき最高の技術スタック

私たちが住んでいる世界を確実に理解するには、データが必要です。ただし、空間参照がない場合、このデータは地理的コンテキストがないと役に立たなくなる可能性があります。

Language