C++ 初始化列表中的就地向量构造(对于具有构造函数参数的类)
可能重复:C++ 初始化列表中的就地向量构造(对于具有构造函数参数的类),c++,c++11,initialization,initializer-list,C++,C++11,Initialization,Initializer List,可能重复: 编辑1:请考虑重新开放投票:我的问题强调地方建设。移动构造是另一种选择,但不是这个问题的内容。谢谢你的回答 编辑2:因为我不能回答这个问题(它被关闭了),我在这里发布了我自己的建议。下面的答案不如我接受的答案好,但可能对其他人有用。至少只调用移动构造函数: 通过std::initializer_list构造对象与从任何其他对象构造对象没有什么不同。std::initializer_list不是一个神秘的幻觉结构;它是一个活生生的、呼吸的C++对象(虽然是临时对象)。因此,它遵循规则
编辑1:请考虑重新开放投票:我的问题强调地方建设。移动构造是另一种选择,但不是这个问题的内容。谢谢你的回答 编辑2:因为我不能回答这个问题(它被关闭了),我在这里发布了我自己的建议。下面的答案不如我接受的答案好,但可能对其他人有用。至少只调用移动构造函数:
通过
std::initializer_list
构造对象与从任何其他对象构造对象没有什么不同。std::initializer_list
不是一个神秘的幻觉结构;它是一个活生生的、呼吸的C++对象(虽然是临时对象)。因此,它遵循规则的生活,呼吸C++对象的所有规则。
聚合初始化可以有效地省略复制/移动,因为它是聚合初始化,一种纯粹的编译时构造<代码>标准::向量是很多东西;聚合和纯编译时构造不在其中。因此,为了使它自己从给定的东西初始化,它必须执行实际的C++代码,而不是编译时的东西。它必须迭代初始值设定项列表的每个元素,并复制或移动这些值。后者是不可能的,因为std::initializer\u list
不提供对其成员的非const
访问
初始值设定项列表初始化看起来像聚合初始化,而不是像它那样执行。这就是在代码段中创建运行时动态抽象(如std::vector
列表初始化std::vector
)的成本,这与执行以下操作无异(如果initializer\u List
具有公共非显式构造函数或std::vector
接受数组引用):
只是
typedef T const* iterator;
因此,从std::initializer\u列表中移出也是毫无疑问的
现在,有解决办法吗?是的,有,事实上,这是一个相当简单的问题
我们希望有一个自由函数,它接受一个容器和一个元组,元组的数量等于您要放置的元素的数量。元组容器将参数传递给容器类型的构造函数。理论上容易,实践上容易(代码中的索引==seq
和构建索引==gen_seq
):
#包括
#包括
#包括
使用别名=T的模板;
使用RemoveRef=typename std::remove_reference::type的模板;
模板
无效模板(C&C、序列、元组和ts){
c、 后置(std::get(std::forward(ts))…);
}
使用Size=std::tuple\u Size的模板;
模板
空位置返回(C&C、元组和…ts){
c、 保留(sizeof…(元组));
别名{(
炮位向后(c,gen,std::forward(ts))
, '0')...};
}
上面的代码调用emplace\u back\u one
精确地sizeof…(Tuples)
次,按照传递给emplace\u back的顺序一次传递一个tuple
。此代码也按从左到右的顺序排列,这意味着构造函数的调用顺序与传递元组的顺序相同emplace\u back\u one
然后简单地用索引技巧解压元组,并将参数传递给c。emplace\u back
我认为第一个构造函数正在构造初始化列表,然后向量完美地从中移动构造。我不确定,但这是有意义的。它不是重复的,我是在进行就地初始化。移动施工将是第二种选择。没错,这个问题的首要答案有助于解释发生了什么。这并不意味着问题是一样的。@JohanLundberg:如果你不能用初始值设定项列表移动初始化向量,你当然不能用一个初始值设定项列表放置它。我认为这可以更好地解释可能的原理(尽管我发现当前的行为不直观)。你为什么要避免移动构造?emplace解决方案也要求可以访问移动构造函数或复制构造函数(如果前者正在抛出),即使它没有被调用。聚合初始化实际上不是编译时构造<数组a{{{1,2},{3,4},{5,6}}代码>当然需要运行时构造函数调用和动态分配。@ XEO:但仅仅是因为中间有一个非聚集。一旦进入向量
,它就不再是聚合初始化。初始化器列表机制的全部要点是它是可读的。如果你想让这个列表包含forward\u as\u tuple
内容,每个元素有一个转发调用,那么你最好有一系列emplace\u back
语句。至少更短了。@Nicolas,是的,但这会自动写那些emplace_回叫,编译时,为什么不呢?实际上,我可能只是在运行时进行迭代,假设构造比循环和参数传递要昂贵得多。@JohanLundberg:“是的,但这会在编译时自动编写emplace_回调,为什么不呢?”因为它在代码中比一系列的v.emplace\u back
调用占用更多的物理空间。@Nicol:当然,当前的表单可能有点冗长,但您可以随时修改Boost.Preprocessor,然后编写emplace\u back(v,(1,2)(3,4)(5,6))
,这实际上应该不太难做到另外,在通用代码中,您可能已经有了这些元组,我认为拥有它们非常好。另外,如果您没有临时文件,或者希望专门将内容移动到元素中,则可以使用std::tie
,这样就不会那么冗长了。@Xeo:您的链接不再有效。你能重新发布完整的例子吗?
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);
std::vector<A2> k{{2,3},{4,5},std::move(A2{8,9})};
#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
// 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::initializer_list<T>::iterator
typedef T const* iterator;
#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')...};
}