初始化列表中的就地向量构造(对于带有构造函数参数的类)

Joh*_*erg 9 c++ initialization initializer-list c++11

可能重复:
我可以列出 - 初始化只移动类型的向量吗?

编辑1:请考虑重新公开投票:我的问题强调就地建设.移动施工是另一种选择,但不是这个问题的内容.谢谢你的回答!

编辑2:因为我无法回答这个问题(它已经关闭)我在这里发表我自己的建议.以下不如我接​​受的答案好,但可能对其他人有用.至少只调用移动构造函数:

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)) ;
}
Run Code Online (Sandbox Code Playgroud)

原帖:

在考虑这个问题的答案时:在STL矢量数组中初始化类我发现我找不到从初始化列表中获取矢量的就地构造的方法.我错过了什么?

现在想要更清楚,我希望这(完全正确)初始化

std::vector<A2> k{{2,3},{4,5},{8,9}};
Run Code Online (Sandbox Code Playgroud)

有一个更类似的效果:

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

但是,在第一种情况下,在插入时临时为复制构造函数调用A2.有没有办法避免这种情况?标准说什么?

我拼命地试了一下

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

但是这会产生对移动构造函数的额外调用,这也是我没想到的.我只是想明确暗示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);

}
Run Code Online (Sandbox Code Playgroud)

输出:

     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
Run Code Online (Sandbox Code Playgroud)

Nic*_*las 12

构造对象via std::initializer_list与从任何其他对象构造对象没有什么不同.这std::initializer_list不是一个神秘的,幻想的结构; 它是一个生机勃勃的C++对象(虽然是临时的).因此,它遵循常规生活,呼吸C++对象的所有规则.

聚合初始化可以有效地忽略复制/移动,因为它是聚合初始化,纯粹的编译时构造.std::vector有很多东西; 聚合和纯编译时构造不在其中.因此,为了使它从给定的内容初始化,它必须执行实际的C++代码,而不是编译时的东西.它必须遍历每个元素initializer_list并复制这些值或移动它们.后者是不可能的,因为std::initializer_list不提供const对其成员的不访问.

初始化列表初始化意味着看起来像聚合初始化,而不是像它一样执行.这就是拥有运行时,动态抽象的成本std::vector.


Xeo*_*Xeo 5

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) });
Run Code Online (Sandbox Code Playgroud)

std::vector实际上,构建一个可以利用实现的特殊方法并非如此.列表初始化是"统一"初始化类型的通用方法.因此,它不可能std::vector与任何其他用户定义的类型不同.因此,让OP中的构造进行安置构造是不可能的.

现在,支持数组(或任何常量数组)可以通过实现放入只读存储器中,这就是原因所在

std::initializer_list<T>::iterator
Run Code Online (Sandbox Code Playgroud)

只是

typedef T const* iterator;
Run Code Online (Sandbox Code Playgroud)

所以搬出std::initializer_list也是不可能的.

现在,有解决方案吗?是的,实际上有,而且这是一个相当容易的!

我们希望有一个自由函数,它使一个容器和一些元组数等于你想要放置的元素数.元组容器是容器类型的构造函数的参数.理论上很容易,在索引技巧的实践中很容易(代码中的位置indices == seqbuild_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')...};
}
Run Code Online (Sandbox Code Playgroud)

活生生的例子与实施seqgen_seq.

上面的代码emplace_back_one完全调用sizeof...(Tuples)时间,按照传递给的顺序一次传递一个元组emplace_back.此代码也从左到右排序,这意味着构造函数的调用顺序与传递元组的顺序相同.emplace_back_one然后简单地用索引技巧解包元组并将参数传递给c.emplace_back.