web-dev-qa-db-ja.com

演算子オーバーロードの基本的な規則と慣用句は何ですか?

注:回答は 特定の順序 で指定されていますが、多くのユーザーは回答時間ではなく投票に従って回答をソートするため、 回答のインデックス は、最もわかりやすい順序で:

(注:これは Stack OverflowのC++ FAQ へのエントリであることを意味しています。この形式で FAQ を提供するという考えを批判したい場合は、それから これをすべて開始したmetaへの投稿 で行うことができます。その質問に対する回答は C++ chatroom で監視され、ここで FAQ というアイデアが始まりました。そもそも、あなたの答えはアイデアを思いついた人たちに読まれる可能性が非常に高いです。)

2010
sbi

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

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

割り当て演算子

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

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

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

ビットシフト演算子<<および>>は、Cから継承するビット操作関数のハードウェアインターフェイスで使用されていますが、ほとんどのアプリケーションでストリーム入力および出力演算子がオーバーロードされているため、より普及しています。ビット操作演算子としてのオーバーロードのガイダンスについては、以下の「二項算術演算子」のセクションを参照してください。オブジェクトを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>>を実装する場合、ストリームの状態を手動で設定する必要があるのは読み取り自体が成功した場合のみですが、結果は予期したものではありません。

関数呼び出し演算子

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

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

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

使用法:

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

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

比較演算子

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

標準ライブラリのアルゴリズム(例:std::sort())およびタイプ(例:std::map)は常にoperator<のみが存在することを期待します。ただし、タイプのユーザーは、他のすべての演算子も存在することを期待します。したがって、operator<を定義する場合は、演算子のオーバーロード、および他のすべてのブール比較演算子も定義します。それらを実装する標準的な方法は次のとおりです。

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進ブール演算子(||&&)をオーバーロードする構文は、比較演算子の規則に従います。ただし、veryは、これらの合理的なユースケースを見つける可能性は低い2

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

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

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

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

算術演算子

単項算術演算子

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

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

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

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

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

二項算術演算子

二項算術演算子については、3番目の基本ルール演算子のオーバーロードに従うことを忘れないでください。+を指定する場合は、+=も指定し、-を指定する場合は、-=など。AndrewKoenigは、複合代入演算子を非化合物対応のベースとして使用できることを最初に観察したと言われています。つまり、演算子++=の観点から実装され、--=の観点から実装されます。

経験則によれば、+とそのコンパニオンは非メンバーである必要があり、複合引数の対応するもの(+=など)は左引数を変更してメンバーでなければなりません。 +=および+のコード例は次のとおりです。他の二項算術演算子も同じ方法で実装する必要があります。

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 operator+は、左オペランドをconst参照ではなくcopyで使用することにも注意してください。この理由は、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_typeclass(またはstructまたはunion)タイプの場合、別のoperator->()が再帰的に呼び出され、 operator->()は、非クラス型の値を返します。

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

operator->*()については、 この質問 を参照してください。まれにしか使用されないため、過負荷になることはほとんどありません。実際、イテレーターでさえオーバーロードしません。


変換演算子 に進みます

991
sbi

C++における演算子のオーバーロードの3つの基本規則

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

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

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

  3. 常に関連する一連の操作からすべてを提供します。
    演算子は互いに関係していますそして他の操作に。あなたの型がa + bをサポートしていれば、ユーザーもa += bを呼び出せると期待するでしょう。それが接頭辞の増加++aをサポートするなら、彼らはa++が同様に働くことを期待するでしょう。彼らがa < bかどうかをチェックできれば、彼らはa > bかどうかもチェックできることを最も確実に期待するでしょう。あなたの型をコピー構築できれば、代入もうまくいくと期待されます。


会員と非会員の決定 に進みます。

464
sbi

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

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

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

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

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

ただし、これらすべてをcanオーバーロードできるという事実は、shouldオーバーロードすべきという意味ではありません。演算子のオーバーロードの基本的な規則を参照してください。

C++では、演算子は特別な名前を持つ関数の形式でオーバーロードされます。他の関数と同様に、オーバーロードされた演算子は一般に、左オペランドの型のメンバー関数として実装できますまたはas非メンバー関数どちらを使用するかを自由に選択できるか、使用するかは、いくつかの基準によって決まります。2 単項演算子@3オブジェクトxに適用され、operator@(x)またはx.operator@()として呼び出されます。オブジェクトxおよびyに適用されるバイナリ中置演算子@は、operator@(x,y)またはx.operator@(y)として呼び出されます。4

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

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

2これは、このFAQの 後の部分 で説明されています。

3@はC++では有効な演算子ではないため、プレースホルダーとして使用しています。

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


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

252
sbi

会員と非会員の決定

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

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

メンバ関数と非メンバ関数のどちらとして実装するかを選択する必要があるすべての演算子に対して、以下の経験則を使用を決定します。

  1. それが単項演算子である場合は、member関数として実装してください。
  2. 二項演算子が両方のオペランドが等しく(それらを変更しないまま)を扱う場合、この演算子をnon-member関数として実装します。
  3. 二項演算子がnotの両方のオペランドを扱う場合equal(通常は左のオペランドを変更します)オペランドの専用部分にアクセスする必要がある場合は、左のオペランドの型のmember関数にします。

もちろん、すべての経験則と同様に、例外もあります。あなたがタイプを持っているなら

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

c ++ではenum型はメンバ関数を持つことができないため、これをメンバ関数としてオーバーロードすることはできません。だからあなたは自由な関数としてそれをオーバーロードする必要があります。また、クラステンプレート内にネストされたクラステンプレートのoperator<()は、クラス定義内でメンバ関数としてインラインで実行されると、読み書きがはるかに簡単になります。しかし、これらは本当にまれな例外です。

(ただし、ifを例外とする場合は、メンバー関数の場合は暗黙のconst引数になるオペランドに対するthisnessの問題を忘れないでください。非メンバー関数としての演算子にはconst参照としてのその左端の引数。メンバー関数と同じ演算子は、*thisconst参照にするために、最後にconstを持つ必要があります。)


オーバーロードする一般的な演算子 に進みます。

225
sbi

変換演算子(ユーザー定義変換とも呼ばれます)

C++では、変換演算子を作成できます。変換演算子は、コンパイラが自分の型と他の定義済み型との間で変換を行えるようにする演算子です。暗黙的変換と明示的変換の2種類の変換演算子があります。

暗黙の変換演算子(C++ 98/C++ 03およびC++ 11)

暗黙の変換演算子を使うと、コンパイラはユーザー定義型の値を暗黙的に他の型に変換することができます(intlongの間の変換)。

以下は、暗黙の変換演算子を持つ単純なクラスです。

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

1引数コンストラクタのような暗黙の変換演算子は、ユーザー定義の変換です。オーバーロードされた関数への呼び出しを照合しようとすると、コンパイラは1つのユーザー定義変換を許可します。

void f(const char*);

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

最初はこれは非常に役立つように思えますが、これに関する問題は、暗黙的な変換が予期されていないときにも開始されることです。次のコードでは、void f(const char*)左辺値 ではないため、my_string()が呼び出されます。最初のものは一致しません。

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

f(my_string());

初心者は簡単にこれを誤解し、経験豊富なC++プログラマでさえも、コンパイラが彼らが疑っていなかった過負荷を選ぶので時々驚きます。これらの問題は、明示的な変換演算子によって軽減できます。

明示的な変換演算子(C++ 11)

暗黙的な変換演算子とは異なり、明示的な変換演算子は、期待していないときには起動しません。以下は、明示的な変換演算子を持つ単純なクラスです。

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

explicitに注目してください。暗黙の変換演算子から予期しないコードを実行しようとすると、コンパイラエラーが発生します。

 prog.cpp:関数 'int main()'の場合:
 prog.cpp:15:18:エラー: 'f(my_string)'の呼び出しに対応する関数がありません
 prog.cpp:15:18:note:候補は以下のとおりです。
 prog.cpp:11:10:note:void f(my_string&)
 prog.cpp:11:10:note:不明引数1を 'my_string'から 'my_string&'に変換する
 prog.cpp:12:10:note:void f(const char *)
 prog.cpp:12:10:note:no引数1の 'my​​_string'から 'const char *'への既知の変換

明示的キャスト演算子を呼び出すには、static_cast、Cスタイルのキャスト、またはコンストラクタスタイルのキャスト(つまりT(value))を使用する必要があります。

ただし、これには1つ例外があります。コンパイラはboolに暗黙的に変換できます。さらに、コンパイラーはboolに変換した後に別の暗黙の変換を行うことはできません(コンパイラーは一度に2つの暗黙の変換を行うことができますが、最大で1つのユーザー定義変換のみを行うことができます)。

コンパイラはboolを "過去"にキャストしないため、明示的な変換演算子で Safe Boolイディオム の必要性がなくなりました。たとえば、C++ 11より前のスマートポインタは、Safe Boolイディオムを使用して整数型への変換を防いでいました。 C++ 11では、スマートポインタは明示的にboolに型を変換した後にコンパイラが暗黙的に整数型に変換することを許可されていないため、明示的な演算子を使用します。

オーバーロードnewdelete に進みます。

152
JKor

newおよびdeleteのオーバーロード

注:これは、syntaxのみを扱い、newおよびdeleteをオーバーロードする実装そのようなオーバーロードされた演算子。オーバーロードのセマンティクスnewdeleteはそれぞれのFAQに値します、演算子オーバーロードのトピック内では決してできない正義感。

基礎

C++では、新しい式のようにnew T(arg)のように書くと、この式が評価されると2つのことが起こります:最初operator newを呼び出して生メモリを取得し、次にTの適切なコンストラクタを呼び出してこの生メモリを有効なオブジェクトに変換します。同様に、オブジェクトを削除すると、まずそのデストラクタが呼び出され、次にメモリがoperator deleteに返されます。
C++を使用すると、メモリ管理と、割り当てられたメモリでのオブジェクトの構築/破棄の両方の操作を調整できます。後者は、クラスのコンストラクターとデストラクターを作成することによって行われます。メモリ管理の微調整は、独自のoperator newおよびoperator deleteを記述することにより行われます。

演算子のオーバーロードの最初の基本ルールであるしないでください –は、特にnewdeleteのオーバーロードに適用されます。これらの演算子をオーバーロードするほとんど唯一の理由は、パフォーマンスの問題およびメモリの制約、および多くの場合、アルゴリズムの変更のような他のアクションは、使用するよりもはるかにより高いコスト/ゲイン比を提供しますメモリ管理の微調整。

C++標準ライブラリには、定義済みのnewおよびdelete演算子のセットが付属しています。最も重要なものは次のとおりです。

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(); 

最初の2つはオブジェクトのメモリを割り当て/割り当て解除し、後の2つはオブジェクトの配列に割り当てます。これらの独自のバージョンを提供する場合、それらはオーバーロードではなく、置換を標準ライブラリから提供します。
operator newをオーバーロードする場合、呼び出そうとはしなくても、常に一致するoperator deleteもオーバーロードする必要があります。その理由は、新しい式の評価中にコンストラクターがスローした場合、ランタイムシステムは、オブジェクトを作成するためにメモリを割り当てるために呼び出されたoperator deleteに一致するoperator newにメモリを返すからです。一致するoperator delete、デフォルトが呼び出されますが、これはほとんど常に間違っています。
newdeleteをオーバーロードする場合は、配列バリアントもオーバーロードすることを検討する必要があります。

配置new

C++では、newおよびdelete演算子が追加の引数を取ることができます。
いわゆる新しい配置により、特定のアドレスにオブジェクトを作成して、次の場所に渡すことができます。

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

標準ライブラリには、このためのnewおよびdelete演算子の適切なオーバーロードが付属しています。

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(); 

上記のnew配置のサンプルコードでは、Xのコンストラクターが例外をスローしない限り、operator deleteは呼び出されないことに注意してください。

newdeleteを他の引数でオーバーロードすることもできます。配置newの追加の引数と同様に、これらの引数もキーワードnewの後の括弧内にリストされています。単に歴史的な理由から、そのようなバリアントは、引数が特定のアドレスにオブジェクトを配置するためのものではない場合でも、しばしば配置新規とも呼ばれます。

クラス固有の新規および削除

最も一般的には、特定のクラスまたは関連するクラスのグループのインスタンスが頻繁に作成および破棄され、ランタイムシステムのデフォルトのメモリ管理が調整されていることが測定により示されているため、メモリ管理を微調整する必要があります。一般的なパフォーマンス、この特定のケースでは非効率的に対処します。これを改善するために、特定のクラスのnewをオーバーロードして削除できます。

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);
    // ... 
}; 

このようにオーバーロードされると、newおよびdeleteは静的メンバー関数のように動作します。 my_classのオブジェクトの場合、std::size_t引数は常にsizeof(my_class)になります。ただし、これらの演算子は、派生クラスの動的に割り当てられたオブジェクトに対しても呼び出されます。この場合、それよりも大きい可能性があります。

グローバル新規および削除

グローバルnewをオーバーロードして削除するには、標準ライブラリの定義済みの演算子を独自のものに置き換えるだけです。ただし、これを行う必要はほとんどありません。

144
sbi

オブジェクトをストリーミングするoperator<<関数をstd::coutまたはファイルにストリーミングできないのはなぜですか?

あなたが持っているとしましょう:

struct Foo
{
   int a;
   double b;

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

それを考えると、あなたは使用することはできません:

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

operator<<Fooのメンバー関数としてオーバーロードされているので、演算子のLHSはFooオブジェクトでなければなりません。つまり、次のものを使用する必要があります。

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

これは非常に直感的ではありません。

非メンバ関数として定義した場合、

struct Foo
{
   int a;
   double b;
};

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

あなたが使用できるようになります:

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

これはとても直感的です。

37
R Sahu