32

重複の可能性:
ベクトルが大きくなったときに移動セマンティクスを適用するにはどうすればよいですか?

insertpush_backおよびemplace_back)は、の再割り当てを引き起こす可能性がありstd::vectorます。次のコードは、コンテナの再割り当て中に要素を移動するのではなく、要素をコピーするのを見て困惑しました。

#include <iostream>
#include <vector>

struct foo {
    int value;

    explicit foo(int value) : value(value) {
        std::cout << "foo(" << value << ")\n";
    }

    foo(foo const& other) noexcept : value(other.value) {
        std::cout << "foo(foo(" << value << "))\n";
    }

    foo(foo&& other) noexcept : value(std::move(other.value)) {
        other.value = -1;
        std::cout << "foo(move(foo(" << value << "))\n";
    }

    ~foo() {
        if (value != -1)
            std::cout << "~foo(" << value << ")\n";
    }
};

int main() {
    std::vector<foo> foos;
    foos.emplace_back(1);
    foos.emplace_back(2);
}

特定のコンパイラ(GCC 4.7)を使用している特定のマシンでは、次のように出力されます。

foo(1)
foo(2)
foo(foo(1))
~foo(1)
~foo(1)
~foo(2)

ただし、コピーコンストラクター(foo(foo const&) = delete;)を削除すると、次の(予期される)出力が生成されます。

foo(1)
foo(2)
foo(move(foo(1))
~foo(1)
~foo(2)

何故ですか?一般的に、移動はコピーよりも効率的であるか、少なくともそれほど効率的ではないでしょうか。

GCC 4.5.1が期待どおりの動作をすることは注目に値します。これは、GCC 4.7でのリグレッションなのか、それとも、コンパイラが私のオブジェクトのコピーが安価であると認識しているため、巧妙な最適化なのか(しかし、どうやって?!)?

また、挿入の前に実験的にを配置することにより、これ再割り当てによって引き起こされていることを確認したことにも注意してください。foos.reserve(2);これにより、コピーも移動も実行されません。

4

3 に答える 3

13

簡単に言えば、@BenVoigtは基本的に正しいと思います。

reserve(§23.3.6.3/ 2)の説明では、次のように述べています。

CopyInsertable以外のタイプのmoveコンストラクター以外で例外がスローされた場合、影響はありません。

[そしてresize§23.3.6.3/12の記述は同じことを要求します。]

これは、TがCopyInsertableの場合、強力な例外安全性が得られることを意味します。それを保証するために、それは、移動構造が決して投げられないと(不特定の手段によって)推論する場合のみ、移動構造を使用することができます。ただし、そのために必要または十分であるという保証はありません。TがCopyInsertableの場合、常にコピー構造を使用することを選択できます基本的に、何が起こっているのかというと、標準ではコピー構造のようなセマンティクスが必要です。コンパイラーは、as-ifルールの下でのみ移動構文を使用でき、そのオプションをいつ実行するか、または実行するかどうかを自由に定義できます。throw()noexcept

TがCopyInsertableでない場合、再割り当てでは移動構文使用されますが、例外の安全性は、Tの移動コンストラクターがスローできるかどうかによって異なります。スローされない場合は、強力な例外安全性が得られますが、スローされた場合は、そうではありません(おそらく基本的な保証は得られると思いますが、それでも、間違いなくそれ以上は得られません)。

于 2012-04-12T16:55:36.230 に答える
8

Tip-of-trunk clang + libc ++は以下を取得します:

foo(1)
foo(2)
foo(move(foo(1))
~foo(2)
~foo(1)

移動コンストラクターからを削除するnoexceptと、コピーソリューションが得られます。

foo(1)
foo(2)
foo(foo(1))
~foo(1)
~foo(2)
~foo(1)
于 2012-04-12T16:59:33.173 に答える
6

これはリグレッションではなく、バグ修正です。標準では、std::vectorは非スローである要素移動コンストラクターのみを優先することを指定しています。

この説明このバグレポートも参照してください。

この質問も関連しています。

于 2012-04-12T16:27:39.110 に答える