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

时间:2012-10-30 22:54:28

标签: c++ c++11 initialization initializer-list

  

可能重复:
  Can I list-initialize a vector of move-only type?

编辑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)) ;
}

原帖:

在考虑这个问题的答案时:Initialization of classes within an STL array of vectors我发现我找不到从初始化列表中获取向量的就地构造的方法。我错过了什么?

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

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调用 copy 构造函数。有没有办法避免这种情况?标准说了什么?

我拼命试试

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

2 个答案:

答案 0 :(得分:12)

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

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

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

答案 1 :(得分: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) });

构建一个可以利用实现的std::vector并不是一种特殊的方法。列表初始化是“统一”初始化类型的通用方法。因此,除了任何其他用户定义的类型之外,它无法使std::vector任何不同。因此,在OP中使用构造进行安装构造是不可能的。

现在,支持数组(或任何常量数组)可能会被实现放入只读内存中,这就是

的原因。
std::initializer_list<T>::iterator

只是

typedef T const* iterator;

所以离开std::initializer_list也是不可能的。

现在,有解决方案吗?是的,实际上有,而且这是一个相当简单的事情!

我们希望有一个自由函数,它使一个容器和一些元组数量等于你想要放置的元素数量。元组容器是容器类型的构造函数的参数。理论上很容易,使用indices trick(代码中的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')...};
}

Live example with the implementation of seq and gen_seq.

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