10

以下の特定のクラスX(明示的に定義されたもの以外の特別なメンバー関数は、この実験には関係ありません):

struct X
{
    X() { }
    X(int) { }
    X(X const&) { std::cout << "X(X const&)" << std::endl; }
    X(X&&) { std::cout << "X(X&&)" << std::endl; }
};

次のプログラムは、型のオブジェクトのベクトルを作成Xし、容量を超えて再割り当てが強制されるようにサイズを変更します。

#include <iostream>
#include <vector>

int main()
{
    std::vector<X> v(5);
    v.resize(v.capacity() + 1);
}

クラスは移動コンストラクターを提供するため、再割り当て後にベクターの以前のコンテンツが新しいストレージに移動Xされることを期待します。驚くべきことに、そうではないようで、得られる出力は次のとおりです。

X(X const&)
X(X const&)
X(X const&)
X(X const&)
X(X const&)

なんで?

4

2 に答える 2

20

C++11 標準の段落 23.3.6.3/14 では、(クラス テンプレートのresize()メンバー関数について) 次のように指定されています。vector<>

備考: 非のムーブ コンストラクター以外によって例外がスローされた場合、CopyInsertable T 影響はありません

言い換えれば、これは for X(これは であるCopyInsertable)が強い保証resize()を提供することを意味します: それは成功するか、ベクトルの状態を変更しないままにします。

この保証を満たすために、実装は通常、コピー アンド スワップ イディオムを採用します。スローのコピー コンストラクターの場合X、元のベクターの内容をまだ変更していないため、約束は守られます。

ただし、ベクターの以前のコンテンツがコピーされるのではなく新しいストレージに移動されムーブ コンストラクターがをスローした場合、ベクターの元のコンテンツが不可逆的に変更されます。

したがって、実装では、移動コンストラクターが をスローしないことがわかっている場合を除き、Xベクターのコンテンツを新しいストレージに安全に転送するために のコピー コンストラクターが使用されます。この場合、前の要素から安全に移動できます。

のムーブ コンストラクターの定義に小さな変更を加えてX(それを としてマークするnoexcept)、実際、プログラムの出力は期待されるものになりました。

struct X
{
    X() { }
    X(int) { }
    X(X const&) { std::cout << "X(X const&)" << std::endl; }
    X(X&&) noexcept { std::cout << "X(X&&)" << std::endl; }
//         ^^^^^^^^
};
于 2013-03-31T15:26:27.477 に答える
5

例外の保証について考えてみてください。再割り当て中に例外が発生した場合、ベクトルは変更されないままにしておく必要があります。これは、要素をコピーし、コピー全体が成功するまで古いセットを保持することによってのみ保証できます。

移動コンストラクターがスローしないことがわかっている場合にのみ、要素を新しい場所に安全に移動できます。これを実現するには、ムーブ コンストラクターを宣言しnoexceptます。

于 2013-03-31T15:30:14.127 に答える