🎃

[C++] コピーコンストラクタ・代入演算子の呼ばれるタイミングを学ぶ

2023/04/28に公開

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