编辑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
答案 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 == 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')...};
}
Live example with the implementation of seq
and gen_seq
.
上面的代码调用emplace_back_one
完全sizeof...(Tuples)
次,按照传递给emplace_back
的顺序一次传递一个元组。此代码也从左到右排序,这意味着构造函数的调用顺序与传递元组的顺序相同。 emplace_back_one
然后只需使用indices技巧解包元组并将参数传递给c.emplace_back
。