考虑以下代码:
#include <iostream>
#include <vector>
struct C {
C() {}
C(const C&) { std::cout << "A copy was made.\n"; }
C(C&&) {std::cout << "A move was made.\n";}
};
std::vector<C> g() {
std::vector<C> ret {C(), C(), C()};
return ret;
}
std::vector<C> h() {
std::vector<C> ret;
ret.reserve(3);
ret.push_back(C());
ret.push_back(C());
ret.push_back(C());
return ret;
}
int main() {
std::cout << "Test g\n";
std::vector<C> v1 = g();
std::cout << "Test h\n";
std::vector<C> v2 = h();
}使用g++ -std=c++11 main.cpp && ./a.out编译,结果是:
Test g
A copy was made.
A copy was made.
A copy was made.
Test h
A move was made.
A move was made.
A move was made.请注意,这两个函数都使用复制省略,因此不会复制返回的std::vector<C>。
我理解为什么h()使用move-constructor,但是为什么g()使用copy-constructor
来自向量病
(6)初始化程序列表构造函数 以相同的顺序构造带有il中每个元素的副本的容器。
它看起来像初始化程序列表总是复制元素,然后可能意味着initializer-list constructor的性能可能会受到影响,如果C是便宜的移动,但很难复制。
因此,我的问题是:用移动成本低但复制量大的对象初始化容器(例如vector)的首选方法是什么?
发布于 2016-07-19 08:15:53
您可以使用一些样板从初始化程序列表中移动。
template<class T>
struct force_move{
mutable T t;
template<class...Args>
force_move(Args&&...args):
t(std::forward<Args>(args)...)
{}
// todo: code that smartly uses {} if () does not work?
force_move()=default;
force_move(force_move const&)=delete;
template<class U, class...Args>
force_move(std::initializer_list<U> il, Args&&...args):
t(il, std::forward<Args>(args)...)
{}
operator T()const{ return std::move(t); }
};
template<class T>
struct make_container {
std::initializer_list<force_move<T>> il;
make_container( std::initializer_list<force_move<T>> l ):il(l) {}
template<class C>
operator C()&&{
return {il.begin(), il.end()};
}
};使用:
std::vector<C> v=make_container<C>{ {}, {} };这是简洁,有效的,并解决了你的问题。
(可能应该是上面的operator T&&。不太确定,我不太愿意再回一份参考.)
现在,这似乎是个小问题。但是,替代方案太糟糕了。
手动推送/嵌入后的列表很难看,在添加了最大效率的后备要求之后,它变得更加丑陋。天真的il解决方案无法移动。
在我看来,不允许您列出声明实例的元素的解决方案是很尴尬的。您希望能够将内容列表与声明相邻。
另一个“本地列表”选项是创建一个变量函数,它在内部初始化一个std::array (可能是ref ),然后从该数组移动到容器中。然而,这不允许{ {}, {}, {} }样式列表,因此我发现它缺乏。
我们可以这样做:
template<class T, std::size_t N>
std::vector<T> move_from_array( T(&arr)[N] ){
return {std::make_move_iterator(std::begin(arr)), std::make_move_iterator(std::end(arr))};
}然后:
C arr[]={{}, {}, {}};
std::vector<C> v = move_from_array(arr);唯一的缺点是在使用时需要两条语句。但代码并不像我的第一个解决方案那么枯燥。
发布于 2016-07-19 08:22:26
你列表 (在Yakk的答案中禁止体操和mutable ),因为initializer_list的元素被声明为const (被认为是危险的名单?)。
我建议在容器中构造具有聚合初始化(即经典数组或std::array )的对象,然后从移动迭代器构造向量。
std::vector<C> h() {
C[] arr{C(), C(), C()};
return std::vector<C>(
std::make_move_iterator(std::begin(arr)),
std::make_move_iterator(std::end(arr)));
}发布于 2016-07-19 08:16:16
不幸的是std::initializer_list 不适用于移动语义。
如果您需要向构造函数提供参数,我将使用emplace_back,它构造适当的元素:
std::vector<C> h() {
std::vector<C> ret;
ret.reserve(3);
ret.emplace_back(arg1);
ret.emplace_back(arg2,arg3);
ret.emplace_back(0,12);
return ret;
}https://stackoverflow.com/questions/38452645
复制相似问题