9

重複の可能性:
移動専用型のベクトルをリスト初期化できますか?

編集 1: 再投票を検討してください: 私の質問はインプレース建設を強調しています。ムーブ コンストラクションは代替案ですが、この質問の対象ではありません。答えてくれてありがとう!

編集 2: この質問に答えられないので (締め切られました)、私自身の提案をここに投稿します。以下は、私が受け入れた回答ほど良くはありませんが、他の人にとっては役立つかもしれません。少なくとも move コンストラクターのみが呼び出されます。

std::vector<A2> vec;
{
  std::array<A2,3> numbers{{{2,3},{5,6},{7,8}}};
  vec.reserve(numbers.size());
  for (auto &v: numbers) vec.emplace_back(std::move(v)) ;
}

元の投稿:

ベクトルの STL 配列内でのクラスの初期化という質問への答えを考えたとき、初期化リストからベクトルのインプレース構築を取得する方法が見つからないことがわかりました。私は何が欠けていますか?

より明確にするために、この(完全に正しい)初期化が必要です

std::vector<A2> k{{2,3},{4,5},{8,9}};

これに似た効果を得るには:

  std::vector<A2> k2;
  k2.reserve(3);
  k2.emplace_back(2,3);
  k2.emplace_back(4,5);
  k2.emplace_back(8,9);

ただし、最初のケースでは、挿入中に一時的に A2 に対してコピーコンストラクターが呼び出されます。それを回避する方法はありますか?基準は何と言っていますか?

必死にやってみた

std::vector<A2> k{{2,3},{4,5},std::move(A2{8,9})};

しかし、それは移動コンストラクターへの追加の呼び出しを生成します。これも私が予期していなかったものです。A2 が一時的なものであることを明示的にほのめかしたかっただけで、暗示されていると思っていたものです。

完全な例:

#include <vector>
#include <iostream>

struct A2 {
  int mk;
  int mj;
  A2(int k,int j) : mk(k),mj(j) {
    std::cout << "     constr for "<<this<< ":"<< mk<<std::endl;
  }
  A2(const A2& a2) {
    mk=a2.mk;
    mj=a2.mj;    
    std::cout << "copy constr for "<<this<< ":" << mk<<std::endl;
  }
  A2(A2&& a2) noexcept  {
    mk=std::move(a2.mk);
    mj=std::move(a2.mj);
    std::cout << "move constr for "<<this<< ":"<< mk<<std::endl;
  }
};

struct Ano {
  Ano() {
    std::cout << "     constr for "<<this <<std::endl;
  }
  Ano(const Ano& ano) {
    std::cout << "copy constr for "<<this<<std::endl;
  }
  Ano(Ano&& ano) noexcept  {
    std::cout << "move constr for "<<this<<std::endl;
  }
};


int main (){
  // here both constructor and copy constructor is called:
  std::vector<A2> k{{2,3},{4,5},std::move(A2{8,9})};

  std::cout << "......"<<std::endl;
  std::vector<A2> k2;
  k2.reserve(3);
  // here (naturally) only constructor is called:
  k2.emplace_back(2,3);
  k2.emplace_back(4,5);
  k2.emplace_back(8,9);

  std::cout << "......"<<std::endl;  
  // here only constructor is called:
  std::vector<Ano> anos(3);

}

出力:

     constr for 0xbf9fdf18:2
     constr for 0xbf9fdf20:4
     constr for 0xbf9fdf0c:8
move constr for 0xbf9fdf28:8
copy constr for 0x90ed008:2
copy constr for 0x90ed010:4
copy constr for 0x90ed018:8
......
     constr for 0x90ed028:2
     constr for 0x90ed030:4
     constr for 0x90ed038:8
......
     constr for 0x90ed048
     constr for 0x90ed049
     constr for 0x90ed04a
4

2 に答える 2

12

を介したオブジェクトのstd::initializer_list構築は、他のオブジェクトからオブジェクトを構築することと同じです。これstd::initializer_listは神秘的で幻想的な構成ではありません。これは、生きている、呼吸するC ++オブジェクトです(一時的なものですが)。このように、それは通常の生活、呼吸するC++オブジェクトのすべての規則に従います。

集約初期化は、純粋にコンパイル時の構成である集約初期化であるため、コピー/移動を効果的に排除できます。std::vector多くのものです。アグリゲートと純粋なコンパイル時の構成はそれらの中にありません。したがって、与えられたものからそれ自体を初期化するためには、コンパイル時のものではなく、実際のC++コードを実行する必要があります。の各要素を反復処理し、initializer_listそれらの値をコピーするか、移動する必要があります。そして、そのメンバーへのstd::initializer_list非アクセスを提供しないので、後者は不可能です。const

初期化子リストの初期化は、集約初期化のように見えることを意図しており、そのように実行することはできません。これは、のようなランタイムで動的な抽象化を行うためのコストですstd::vector

于 2012-10-30T23:57:54.167 に答える
6

スニペットでリストを初期化std::vectorすることは、次のことを行うことと何ら変わりはありません (initializer_list公開された非明示的なコンストラクターがある場合、またはstd::vector配列参照を受け入れた場合)。

// directly construct with the backing array of 'initializer_list'
std::vector<A2> v(alias<A2[]>{ A2(2,3), A2(4,5), A2(8,9) });

std::vector実際、実装を利用できるa を構築する特別な方法ではありません。リスト初期化は、型を「均一に」初期化する汎用的な方法です。そのためstd::vector、他のユーザー定義型と異なる処理を行う方法はありません。そのため、OP の構造を構造に配置することは問題外です。

現在、バッキング配列 (または任意の定数配列) は、実装によって読み取り専用メモリに配置される可能性があります。これが理由です。

std::initializer_list<T>::iterator

ただです

typedef T const* iterator;

したがって、引っ越しstd::initializer_listも問題外です。

さて、解決策はありますか?はい、実際にはかなり簡単です。

コンテナと、配置したい要素の数に等しい数のタプルを取る無料の関数が必要になります。タプルは、コンテナー型のコンストラクターへの引数を格納します。理論的には簡単で、インデックスのトリックを使用すると実際には簡単です(コード内の場所indices == seqと場所build_indices == gen_seq):

#include <type_traits>
#include <tuple>
#include <utility>

template<class T> using alias = T;
template<class T> using RemoveRef = typename std::remove_reference<T>::type;

template<class C, unsigned... Is, class Tuple>
void emplace_back_one(C& c, seq<Is...>, Tuple&& ts){
  c.emplace_back(std::get<Is>(std::forward<Tuple>(ts))...);
}

template<class T> using Size = std::tuple_size<RemoveRef<T>>;

template<class C, class... Tuples>
void emplace_back(C& c, Tuples&&... ts){
  c.reserve(sizeof...(Tuples));
  alias<char[]>{(
    emplace_back_one(c, gen_seq<std::tuple_size<RemoveRef<Tuples>>::value>{}, std::forward<Tuples>(ts))
  , '0')...};
}

seqと を実装した実例gen_seq

上記のコードは、 に渡された順序で一度に 1 つのタプルを渡しながら、emplace_back_one正確に1 回呼び出します。このコードも左から右に配列されています。つまり、コンストラクターは、タプルを渡したのと同じ順序で呼び出されます。次に、単にインデックス トリックを使用してタプルをアンパックし、引数を に渡します。sizeof...(Tuples)emplace_backemplace_back_onec.emplace_back

于 2012-10-31T02:52:06.123 に答える