🎃
[C++] コピーコンストラクタ・代入演算子の呼ばれるタイミングを学ぶ
C++のクラスにはコピー・ムーブコンストラクタやコピー・ムーブ代入演算子等があるが、これらがいつ呼ばれるのか曖昧に理解していたので整理する。
まずは以下のように確認するためにSampleクラスを作ってみて実際にどれが呼ばれているかを確認してみる。
class Sample
{
public:
Sample()
{
std::cout << "call default constructor" << std::endl;
}
Sample(const Sample&)
{
std::cout << "call copy constructor" << std::endl;
}
Sample& operator=(const Sample& other)
{
std::cout << "call copy assignment operator" << std::endl;
return *this;
}
Sample(const Sample&&)
{
std::cout << "call move constructor" << std::endl;
}
Sample& operator=(const Sample&& other)
{
std::cout << "call move assignment operator" << std::endl;
return *this;
}
};
このSampleクラスを使い以下のように書いて何が呼ばれるか確認。
int main()
{
Sample a; // call default constructor
Sample b { a }; // call copy constructor
b = a; // call copy assignment operator
Sample c { std::move(a) }; // call move constructor
c = std::move(a); // call move assignment operator
return 0;
}
動かしてみた結果、以下のような仕様だと分かった。
- オブジェクト生成時に左辺値で引数を渡す -> コピーコンストラクタ
- オブジェクト生成後に=で左辺値を代入 -> コピー代入演算子
- オブジェクト生成時に右辺値で引数を渡す -> ムーブコンストラクタ
- オブジェクト生成後に=で右辺値を代入 -> ムーブ代入演算子
ちなみに以下のようにムーブ系が定義されていない場合コンパイルエラーにはならない。
右辺値で渡していてもコピーのコンストラクタ・代入演算子のコールに置き換わることも分かった。
class Sample
{
public:
Sample()
{
std::cout << "call default constructor" << std::endl;
}
Sample(const Sample&)
{
std::cout << "call copy constructor" << std::endl;
}
Sample& operator=(const Sample& other)
{
std::cout << "call copy assignment operator" << std::endl;
return *this;
}
/* コメントアウト
Sample(const Sample&&)
{
std::cout << "call move constructor" << std::endl;
}
Sample& operator=(const Sample&& other)
{
std::cout << "call move assignment operator" << std::endl;
return *this;
}
*/
};
先ほどのmain処理を実行するとムーブコンストラクタ・ムーブ代入演算子の代わりにコピーコンストラクタ・コピー代入演算子が呼ばれる。
int main()
{
Sample a; // call default constructor
Sample b { a }; // call copy constructor
b = a; // call copy assignment operator
Sample c { std::move(a) }; // call copy constructor
c = std::move(a); // call copy assignment operator
return 0;
}
ムーブでやっているつもりがコピーになってたりしないよう、ムーブはちゃんと定義しておいた方が良さそう。
Discussion