9

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

编辑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 调用复制构造函数。有没有办法避免这种情况?标准是怎么说的?

我拼命尝试

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

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

聚合初始化可以有效地省略复制/移动,因为它是聚合初始化,一个纯粹的编译时构造。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可以利用实现的特殊方法。列表初始化是一种“统一”初始化类型的通用方式。因此,它不可能std::vector与任何其他用户定义的类型有任何不同。因此,让 OP 中的构造进行 emplace 构造是毫无疑问的。

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

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

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

于 2012-10-31T02:52:06.123 回答