使用移动成本低但复制成本高的对象初始化容器的首选方法是什么
What is the preferred way to initialize a container with objects that are cheap to move but heavy to copy
考虑以下代码:
#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
?
来自 vector's doc:
(6) initializer list constructor
Constructs a container with a copy of each of the elements in il, in the same order.
看起来 initializer-list 总是 copy 元素,那么这可能意味着如果 C
很便宜,initializer-list constructor
的性能可能会受到影响移动但复制很重。
所以我的问题是:用移动成本低但复制重的对象初始化容器(例如 vector
)的首选方法是什么?
您可以使用一些样板从初始化列表中移动。
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&&
。不确定,而且我对返回右值引用持怀疑态度...)
现在,这似乎有点乱七八糟。但是,替代方案很糟糕。
手动推送 back/emplace 返回列表很丑陋,并且在您添加储备要求以实现最大效率后变得更丑陋。而且幼稚的il解决方案无法移动。
不让您在声明实例的地方列出元素的解决方案很尴尬,在我看来。您希望能够将内容列表放在声明旁边。
另一种 "local list" 替代方法是创建一个内部初始化 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);
唯一的缺点是在使用时需要两个声明。但是代码没有我的第一个解决方案那么钝。
可惜std::initializer_list
doesn't really work with move semantics.
如果您需要向构造函数提供参数,我会改用 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;
}
你cannot move from an initializer_list (barring gymnastics with mutable
as in Yakk's answer) because the elements of an initializer_list are declared const
(moving elements of an initialization_list considered dangerous?).
我建议在具有聚合初始化的容器中构造对象,即经典数组或 std::array
,然后从 move iterators:
构造向量
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)));
}
考虑以下代码:
#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
?
来自 vector's doc:
(6) initializer list constructor
Constructs a container with a copy of each of the elements in il, in the same order.
看起来 initializer-list 总是 copy 元素,那么这可能意味着如果 C
很便宜,initializer-list constructor
的性能可能会受到影响移动但复制很重。
所以我的问题是:用移动成本低但复制重的对象初始化容器(例如 vector
)的首选方法是什么?
您可以使用一些样板从初始化列表中移动。
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&&
。不确定,而且我对返回右值引用持怀疑态度...)
现在,这似乎有点乱七八糟。但是,替代方案很糟糕。
手动推送 back/emplace 返回列表很丑陋,并且在您添加储备要求以实现最大效率后变得更丑陋。而且幼稚的il解决方案无法移动。
不让您在声明实例的地方列出元素的解决方案很尴尬,在我看来。您希望能够将内容列表放在声明旁边。
另一种 "local list" 替代方法是创建一个内部初始化 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);
唯一的缺点是在使用时需要两个声明。但是代码没有我的第一个解决方案那么钝。
可惜std::initializer_list
doesn't really work with move semantics.
如果您需要向构造函数提供参数,我会改用 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;
}
你cannot move from an initializer_list (barring gymnastics with mutable
as in Yakk's answer) because the elements of an initializer_list are declared const
(moving elements of an initialization_list considered dangerous?).
我建议在具有聚合初始化的容器中构造对象,即经典数组或 std::array
,然后从 move iterators:
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)));
}