2017-07-17 4 views
8

私はコピーを持って/クラスプロービング移動:なぜstd :: vectorは初期化時にコピーを強制しますか?

#include <iostream> 

struct A 
{ 
    A() 
    { 
     std::cout << "Creating A" << std::endl; 
    } 

    ~A() noexcept 
    { 
     std::cout << "Deleting A" << std::endl; 
    } 

    A(const A &) 
    { 
     std::cout << "Copying A" << std::endl; 
    } 

    A(A &&) noexcept 
    { 
     std::cout << "Moving A" << std::endl; 
    } 

    A &operator=(const A &) 
    { 
     std::cout << "Copy-assigning A" << std::endl; 
     return *this; 
    } 

    A &operator=(A &&) noexcept 
    { 
     std::cout << "Move-assigning A" << std::endl; 
     return *this; 
    } 
}; 

そして、私が実行していることを発見した:

#include <vector> 

int main(int, char **) 
{ 
    std::vector<A> v { A() }; 
} 

すると、次の出力を生成します。

Creating A 
Copying A 
Deleting A 
Deleting A 

なぜ初期化しませんオブジェクトを移動するだけですか?私はstd::vectorundesired copies on resizeを作成することがありますが、ご覧のとおり、noexceptを追加すると、ここで助けにならなかったことがわかりました(また、サイズ変更によってコピーが初期化される理由もないと思います)。

私の代わりに次の操作を行う場合は、次の

std::vector<A> v; 
v.push_back(A()); 

私はコピーを得ることはありません。

GCC 5.4およびClang 3.8でテスト済みです。

+9

これは 'std :: initializer_list'コンストラクタが使用されているためです。そのタイプのセマンティクスは残念です。 – StoryTeller

+3

このQ/Aでは状況を説明し、いくつかの回避策を提供しています:https://stackoverflow.com/questions/8468774/can-i-list-initialize-a-vector-of-move-only-type –

+0

@StoryTeller @MM Iそれで、問題は 'std :: vector'ではなく' std :: initializer_list'であり、明らかにrvalue参照を転送できません。誰かが適切な回答を投稿してくれたら、私はそれを受け入れます。 – jdehesa

答えて

9

std::vectorではなく、std::initializer_listです。

std::initializer_listは、要素の配列constによってサポートされています。それは非constのデータへのアクセスを許可しません。

このブロックはデータから移動します。

しかし、これはC++であるので、我々は、この解決することができます:

template<class T, class A=std::allocator<T>, class...Args> 
std::vector<T,A> make_vector(Args&&...args) { 
    std::array<T, sizeof...(Args)> tmp = {{std::forward<Args>(args)...}}; 
    std::vector<T,A> v{ std::make_move_iterator(tmp.begin()), std::make_move_iterator(tmp.end()) }; 
    return v; 
} 

は今、我々が得る:

Creating A 
Moving A 
Moving A 
Deleting A 
Deleting A 
Deleting A 

私たちは、次のことができます。

auto v = make_vector<A>(A()); 

はあなたの要素ごとに1つの余分な動きを与えますその余分なインスタンスを慎重に予約して元に戻します。

template<class T, class A=std::allocator<T>, class...Args> 
std::vector<T,A> make_vector(Args&&...args) { 
    std::vector<T,A> v; 
    v.reserve(sizeof...(args)); 
    using discard=int[]; 
    (void)discard{0,(void(
    v.emplace_back(std::forward<Args>(args)) 
),0)...}; 
    return v; 
} 

Live example of bothは - 単にアクションで最初のものを見るためにv1::ためv2::を交換します。

出力:

Creating A 
Moving A 
Deleting A 
Deleting A 

コンパイラは(我々はそれを証明できても)emplace_backは再割り当てが発生しないことを証明することは難しいかもしれないとして、ここではもう少しベクトルのオーバーヘッドがある可能性がありそう冗長小切手はほとんどの場合、コンパイルされます。 (私の意見では、十分な容量がない場合、UBであるemplace_back_unsafeが必要です)。

Aの余分なセットの損失はおそらくそれに値するでしょう。

もう一つの選択:

手動でどのように多くの要素を指定する必要があり
auto v = make_vector<1,A>({{ A() }}); 

のように使用されて

template<std::size_t N, class T, class A=std::allocator<T>, class...Args> 
std::vector<T,A> make_vector(std::array<T, N> elements) { 
    std::vector<T,A> v{ std::make_move_iterator(elements.begin()), std::make_move_iterator(elements.end()) }; 
    return v; 
} 

。上記のバージョン2と同じくらい効率的です。

関連する問題