コピー代入演算子
コピー代入演算子とは、同じクラス型の引数を受け取り、その引数を変更することなく内容をコピーできる、非テンプレートの非静的メンバ関数で、名前はoperator=です。
目次 |
[編集] 構文
形式的なコピー代入演算子の構文については、関数宣言を参照してください。以下の構文リストは、有効なすべてのコピー代入演算子の構文のサブセットのみを示しています。
return-type operator=(parameter-list ); |
(1) | ||||||||
return-type operator=(parameter-list ) function-body |
(2) | ||||||||
return-type operator=(parameter-list-no-default ) = default; |
(3) | (C++11以降) | |||||||
return-type operator=(parameter-list ) = delete; |
(4) | (C++11以降) | |||||||
return-type class-name ::operator=(parameter-list ) function-body |
(5) | ||||||||
return-type class-name ::operator=(parameter-list-no-default ) = default; |
(6) | (C++11以降) | |||||||
| class-name | - | コピー代入演算子が宣言されているクラス。以下の説明ではクラス型はTとして与えられます。 |
| parameter-list | - | 型がT、T&、const T&、volatile T&、またはconst volatile T&である唯一のパラメーターのパラメーターリスト。 |
| parameter-list-no-default | - | 型がT、T&、const T&、volatile T&、またはconst volatile T&であり、デフォルト引数を持たない唯一のパラメーターのパラメーターリスト。 |
| 関数本体 | - | コピー代入演算子の関数本体。 |
| return-type | - | 任意の型ですが、代入の連鎖を可能にするためにT&が推奨されます。 |
[編集] 説明
struct X { X& operator=(X& other); // copy assignment operator X operator=(X other); // pass-by-value is allowed // X operator=(const X other); // Error: incorrect parameter type }; union Y { // copy assignment operators can have syntaxes not listed above, // as long as they follow the general function declaration syntax // and do not viloate the restrictions listed above auto operator=(Y& other) -> Y&; // OK: trailing return type Y& operator=(this Y& self, Y& other); // OK: explicit object parameter // Y& operator=(Y&, int num = 1); // Error: has other non-object parameters };
コピー代入演算子は、例えば代入式の左辺にオブジェクトが出現する場合など、オーバーロード解決によって選択されたときに呼び出されます。
[編集] 暗黙的に宣言されるコピー代入演算子
クラス型にユーザー定義のコピー代入演算子が提供されていない場合、コンパイラは常に1つをクラスのinline publicメンバーとして宣言します。この暗黙的に宣言されるコピー代入演算子は、以下のすべてが真である場合、T& T::operator=(const T&)の形式を持ちます。
Tの各直接基底Bは、パラメーターがBまたはconst B&またはconst volatile B&であるコピー代入演算子を持っています。- クラス型またはクラス型の配列である
Tの各非静的データメンバーMは、パラメーターがMまたはconst M&またはconst volatile M&であるコピー代入演算子を持っています。
それ以外の場合、暗黙的に宣言されるコピー代入演算子はT& T::operator=(T&)として宣言されます。
これらのルールにより、暗黙的に宣言されるコピー代入演算子はvolatile左辺値引数にバインドすることはできません。
クラスは、例えばT& T::operator=(T&)とT& T::operator=(T)の両方など、複数のコピー代入演算子を持つことができます。ユーザー定義のコピー代入演算子が存在する場合でも、ユーザーはキーワードdefaultを使用して暗黙的に宣言されたコピー代入演算子の生成を強制できます。(C++11以降)
暗黙的に宣言された(または最初の宣言でデフォルト化された)コピー代入演算子は、動的例外指定(C++17まで)noexcept指定(C++17以降)で説明されている例外指定を持ちます。
コピー代入演算子は常にすべてのクラスに対して宣言されるため、基底クラスの代入演算子は常に隠されます。using宣言を使用して基底クラスから代入演算子を導入し、その引数型が派生クラスの暗黙的な代入演算子の引数型と同じになる可能性がある場合、そのusing宣言も暗黙的な宣言によって隠されます。
[編集] 暗黙的に定義されるコピー代入演算子
暗黙的に宣言されたコピー代入演算子が削除も自明でもない場合、それがodr-useされた場合、または定数評価に必要な場合(C++14以降)にコンパイラによって定義されます(つまり、関数本体が生成され、コンパイルされます)。共用体型の場合、暗黙的に定義されるコピー代入は、std::memmoveのようにオブジェクトの表現をコピーします。非共用体クラス型の場合、演算子はオブジェクトの直接基底と非静的データメンバーを初期化順にメンバーごとにコピー代入し、スカラには組み込みの代入を使用し、配列にはメンバーごとのコピー代入を使用し、クラス型にはコピー代入演算子(非仮想で呼び出される)を使用します。
|
クラス
|
(C++14以降) (C++23まで) |
|
クラス |
(C++23から) |
|
|
(C++11以降) |
[編集] 削除されたコピー代入演算子
クラスTの暗黙的に宣言された(または明示的にデフォルト化された)(C++11以降)コピー代入演算子は、以下のいずれかの条件が満たされる場合、未定義(C++11まで)削除されたものとして定義されます(C++11以降)。
-
Tがconst修飾された非クラス型(またはその多次元配列)の非静的データメンバーを持つ。 -
Tが参照型の非静的データメンバーを持つ。 -
Tがクラス型M(またはその多次元配列)の潜在的に構築されるサブオブジェクトを持ち、Mのコピー代入演算子を見つけるために適用されるオーバーロード解決が
- 使用可能な候補をもたらさない場合、または
- サブオブジェクトがバリアントメンバである場合、非トリビアルな関数を選択する場合。
|
クラス |
(C++11以降) |
[編集] 自明なコピー代入演算子
クラスTのコピー代入演算子が自明であるのは、以下のすべてが真である場合です。
- ユーザー提供ではない(つまり、暗黙的に定義されているか、デフォルト化されている)こと。
-
Tに仮想メンバ関数がない; -
Tに仮想基底クラスがない; Tのすべての直接基底に対して選択されたコピー代入演算子が自明であること。Tのすべての非静的クラス型(またはクラス型の配列)メンバーに対して選択されたコピー代入演算子が自明であること。
自明なコピー代入演算子は、std::memmoveのようにオブジェクトの表現をコピーします。C言語と互換性のあるすべてのデータ型(POD型)は自明にコピー代入可能です。
[編集] 適格なコピー代入演算子
|
コピー代入演算子は、ユーザー宣言であるか、または暗黙的に宣言されかつ定義可能である場合、適格です。 |
(C++11まで) |
|
コピー代入演算子は、削除されていない場合、適格です。 |
(C++11以降) (C++20まで) |
|
コピー代入演算子は、以下のすべての条件が満たされる場合、適格です。 |
(C++20以降) |
適格なコピー代入演算子の自明性は、そのクラスが自明にコピー可能な型であるかどうかを決定します。
[編集] 注釈
コピー代入演算子とムーブ代入演算子の両方が提供されている場合、引数が右辺値(無名のテンポラリなどのprvalue、またはstd::moveの結果などのxvalue)の場合はムーブ代入が選択され、引数が左辺値(名前付きオブジェクト、または左辺値参照を返す関数/演算子)の場合はコピー代入が選択されます。コピー代入のみが提供されている場合、すべての引数カテゴリがそれを選択します(引数を値渡しまたはconst参照として取る限り、右辺値はconst参照にバインドできるため)。これにより、ムーブが利用できない場合、コピー代入がムーブ代入のフォールバックとなります。
継承階層内で複数のパスを通じてアクセス可能な仮想基底クラスのサブオブジェクトが、暗黙的に定義されたコピー代入演算子によって複数回代入されるかどうかは未規定です(ムーブ代入についても同様です)。
ユーザー定義のコピー代入演算子の期待される動作に関する詳細については、代入演算子のオーバーロードを参照してください。
[編集] 例
#include <algorithm> #include <iostream> #include <memory> #include <string> struct A { int n; std::string s1; A() = default; A(A const&) = default; // user-defined copy assignment (copy-and-swap idiom) A& operator=(A other) { std::cout << "copy assignment of A\n"; std::swap(n, other.n); std::swap(s1, other.s1); return *this; } }; struct B : A { std::string s2; // implicitly-defined copy assignment }; struct C { std::unique_ptr<int[]> data; std::size_t size; // user-defined copy assignment (non copy-and-swap idiom) // note: copy-and-swap would always reallocate resources C& operator=(const C& other) { if (this != &other) // not a self-assignment { if (size != other.size) // resource cannot be reused { data.reset(new int[other.size]); size = other.size; } std::copy(&other.data[0], &other.data[0] + size, &data[0]); } return *this; } }; int main() { A a1, a2; std::cout << "a1 = a2 calls "; a1 = a2; // user-defined copy assignment B b1, b2; b2.s1 = "foo"; b2.s2 = "bar"; std::cout << "b1 = b2 calls "; b1 = b2; // implicitly-defined copy assignment std::cout << "b1.s1 = " << b1.s1 << "; b1.s2 = " << b1.s2 << '\n'; }
出力
a1 = a2 calls copy assignment of A b1 = b2 calls copy assignment of A b1.s1 = foo; b1.s2 = bar
[編集] 欠陥報告
以下の動作変更を伴う欠陥報告が、以前に公開されたC++標準に遡って適用されました。
| DR | 適用対象 | 公開された動作 | 正しい動作 |
|---|---|---|---|
| CWG 1353 | C++98 | 暗黙的に宣言されたコピー代入演算子が 未定義である条件が多次元配列型を考慮していなかった |
これらの型を考慮する |
| CWG 2094 | C++11 | volatileサブオブジェクトがデフォルト化されたコピー 代入演算子を自明ではないものにした(CWG issue 496) |
トリビアル性は影響を受けない。 |
| CWG 2171 | C++11 | operator=(X&) = defaultは自明ではない | 自明にした |
| CWG 2180 | C++11 | クラスTのデフォルト化されたコピー代入演算子が削除されたものとして定義されていなかったTが抽象クラスで、コピー代入不可な直接仮想基底クラスを持っている場合 |
演算子は定義される この場合、削除されたものとして |
| CWG 2595 | C++20 | コピー代入演算子は、そこにある場合、適格ではなかった より制約が厳しいが、関連する制約を満たさない別のコピー代入演算子 が存在する場合、適格であることは可能 |
適格になることができる この場合に |