読者です 読者をやめる 読者になる 読者になる

ムーブコンストラクタにnoexceptを付けないと呼ばれないことがある

C++

Bug 52745 - GCC4.7 vector uses copy instead of move constructor


GCCに上がってたバグレポ。
GCC 4.6.1から4.7にしたら、ムーブコンストラクタがあるのにコピーコンストラクタが呼ばれるようになったそうです。プログラムは以下:

#include <iostream>
#include <vector>

struct Stuff {
    Stuff() {}
    Stuff(Stuff&&) { std::cout << "Move" << std::endl; }
    Stuff(const Stuff&) { std::cout << "Copy" << std::endl; }
};

int main()
{
    std::vector<Stuff> stuff;

    std::cout << "1" << std::endl;
    stuff.push_back(Stuff());
    std::cout << "2" << std::endl;
    stuff.push_back(Stuff());
    std::cout << "3" << std::endl;
    stuff.push_back(Stuff());
}

GCC 4.6.1の結果:

1
Move
2
Move
Move
3
Move
Move
Move

GCC 4.7の結果:

1
Move
2
Move
Copy
3
Move
Copy
Copy


GCC 4.7でstd::vectorの実装に修正が入り、push_backの内部ではstd::move_if_noexcept()を使用するようになったため、noexceptじゃないムーブコンストラクタが呼ばれなくなったそうです。
コンパイラによって暗黙定義されるムーブコンストラクタはnoexceptですが、自分で定義したときにnoexceptを付けていないのが原因ですね。
以下のように、ムーブコンストラクタにnoexceptを付ければ、GCC 4.6.1と同様、コピーコンストラクタが呼ばれず全てムーブになります:

#include <iostream>
#include <vector>

struct Stuff {
    Stuff() {}
    Stuff(Stuff&&) noexcept { std::cout << "Move" << std::endl; }
    Stuff(const Stuff&) { std::cout << "Copy" << std::endl; }
};

int main()
{
    std::vector<Stuff> stuff;

    std::cout << "1" << std::endl;
    stuff.push_back(Stuff());
    std::cout << "2" << std::endl;
    stuff.push_back(Stuff());
    std::cout << "3" << std::endl;
    stuff.push_back(Stuff());
}
1
Move
2
Move
Move
3
Move
Move
Move