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

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

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは、夫に会ったとき、典型的な交際のアドバイスに逆らいました。

マイケルシーンが非営利の俳優である理由

マイケルシーンが非営利の俳優である理由

マイケルシーンは非営利の俳優ですが、それは正確にはどういう意味ですか?

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

特徴的なスターのコリン・エッグレスフィールドは、RomaDrama Liveでのスリル満点のファンとの出会いについて料理しました!加えて、大会での彼のINSPIREプログラム。

「たどりつけば」をオンラインでストリーミングできない理由

「たどりつけば」をオンラインでストリーミングできない理由

ノーザンエクスポージャーが90年代の最も人気のある番組の1つになった理由を確認するには、Blu-rayまたはDVDプレーヤーをほこりで払う必要があります。

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

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

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

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖は、世界で2番目に大きいボイリング湖です。そこにたどり着くまでのトレッキングは大変で長いですが、努力する価値は十分にあります。

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

サロンからのヘアトリミングや個人的な寄付は、油流出を吸収して環境を保護するのに役立つマットとして再利用できます。

ホワイトハウスの最も記憶に残る結婚式を見てください

ホワイトハウスの最も記憶に残る結婚式を見てください

過去200年以上の間にホワイトハウスで結婚したのはほんの数人です。彼らは誰でしたか、そしてそこで結婚式を獲得するために何が必要ですか?

ジェイ・ブルースはどうやら子供を妊娠することによってメッツから離れて取引されていることを祝った

ジェイ・ブルースはどうやら子供を妊娠することによってメッツから離れて取引されていることを祝った

あなたが一時的に会っていなかったとき。シーズン11-1を開始したチームであるニューヨークメッツは、日曜日の午後にフィラデルフィアで行われた最後の11試合の9試合目を失いました。

スティーブンキングのアウトサイダーはトランプ時代のそれです

スティーブンキングのアウトサイダーはトランプ時代のそれです

スティーブン・キングのアウトサイダーは、多くの点で先祖返りの小説であり、80年代の全盛期から引き裂かれたように見える生き物の特徴であり、おそらくセル以来の彼の最もパルプのような本ですが、今日の恐怖の中で間違いなく設立された作品です。表面上は、形を変えるペニーワイズのような子供たちの殺人者を中心としており、その最も暗い脅威は、封じ込められず、神経質に平凡なものよりも幻想的で打ち負かされません。

スティーブンユニバースは、強烈な内部エピソードのペアで、それ自体のバックストーリーをさりげなく粉砕します

スティーブンユニバースは、強烈な内部エピソードのペアで、それ自体のバックストーリーをさりげなく粉砕します

スティーブンユニバースビーチシティのエピソードが実行されるたびに、いくつかのクライマックスイベントが発生し、スティーブンユニバースのより広い神話に対する理解の一部が失われます。これはあなたが期待していたことですか?今日のエピソードは両方とも、容赦なくゆっくりと、シーズンの終盤の主要な部分を設定する決定的な結論に向かって進みます。そして、ロナウドは、静かな納屋が倒れているのを発見した夜中にスティーブンを捕まえるためにやって来ます。月に。

ディズニーワールドの旅行のヒントを教えてください

ディズニーワールドの旅行のヒントを教えてください

「光が触れるものはすべて私たちの王国です。」今週のHackYour Cityでは、1つのテーマパークを取り上げます。

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya shared a sweet photo in honor of boyfriend Tom Holland's 26th birthday Wednesday

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

シーレン「Ms.JuicyBaby」ピアソンは、先月脳卒中で入院した後、「もう一度たくさんのことをする方法を学ばなければならない」ため、言語療法を受けていることを明らかにしました。

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

オスカー受賞者の世紀半ばの家には、3つのベッドルーム、2つのバス、オーシャンフロントの景色があります。

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、生後4か月の娘、モナコに母乳育児をしていると語った。

Come diventare Web Developer?

Come diventare Web Developer?

Vorresti diventare web developer e non sai da dove cominciare?! Qui troverai tutte le risposte necessarie, anche io non sapevo che strada intraprendere ma voglio aiutarti a non commettere i miei stessi errori. Cosa imparare? Le competenza essenziali per qualsiasi web developer sono almeno tre.

投資ノート:Bioscout AU$300万シード

投資ノート:Bioscout AU$300万シード

Bioscoutは、農家を運転席に置くという使命を負っています。Artesian(GrainInnovate)やUniseedと並んで、最新のシードラウンドでチームを支援できることをうれしく思います。問題真菌症による重大な作物の損失は、農民にとって試練であることが証明されています。

リトルマーケットリサーチ1| 2022年のクイックグリンプス遠隔医療市場

リトルマーケットリサーチ1| 2022年のクイックグリンプス遠隔医療市場

遠隔医療は、パンデミック後の時代では新しいものではなく、時代遅れの分野でもありません。しかし、業界を詳しく見ると、需要と供給の強力な持続可能性と、米国で絶え間ない革命となる強力な潜在的成長曲線を示しています。

スタートアップ資金調達環境:タイのスタートアップエコシステムの次は何ですか?

スタートアップ資金調達環境:タイのスタートアップエコシステムの次は何ですか?

2021年は、世界的なベンチャーキャピタル(VC)の資金調達にとって記録的な年でした。DealStreetAsiaによると、東南アジアも例外ではなく、この地域では年間で記録的な25の新しいユニコーンが採掘されました。

Language