What is the preferred way to initialize a container with objects that are cheap to move around but hard to copy

Consider the code below:

#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(); } 

Compiled with g++ -std=c++11 main.cpp && ./a.out , result:

 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. 

Note that both functions use copy elision, so the returned std::vector<C> not copied.

I understand why h() uses move-constructor , but why g() uses copy-constructor ?

From vector document :

(6) initializer list constructor

Creates a container with a copy of each of the elements in il in the same order.

It seems that the list of initializers always copies elements, this probably means that the performance of the initializer-list constructor can be affected if C cheap to move around, but hard to copy.

So my question is: what is the preferred way to initialize a container (like vector ) with objects that are cheap to move but hard to copy?

+7
c ++ c ++ 11 move initializer-list
source share
3 answers

You can navigate from the list of initializers with a small number of templates.

 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()}; } }; 

Using:

 std::vector<C> v=make_container<C>{ {}, {} }; 

It is concise, effective and solves your problem.

(Maybe it should be operator T&& above). Not sure, and I don't know, ever returning rvalue link ...)

Now it seems a bit hacked. But the alternatives suck.

The manual back back / emplace back list is ugly and becomes uglier after adding fallback requirements for maximum efficiency. And the naive solution il cannot move.

Solutions that do not allow you to list items right where the instance is declared are, in my opinion, inconvenient. You want the content list to be adjacent to the ad.

Another alternative to the โ€œlocal listโ€ is to create a function variable that internally initializes std::array (possibly ref wrappers), which then moves from this array to the container. However, this does not allow style lists { {}, {}, {} } , so I miss.

We could do this:

 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))}; } 

Then:

 C arr[]={{}, {}, {}}; std::vector<C> v = move_from_array(arr); 

The only drawback is the two statements at the point of use. But the code is less dumb than my first decision.

+3
source share

You cannot go from the initializer_list file (prohibition of gymnastics with mutable , as in Yakka's answer), since the initializer_list elements are declared const (are the moving initialization_list elements considered dangerous? ).

I would recommend building your objects in a container with aggregate initialization, i.e. a classic array or std::array , and then constructing a vector from 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))); } 
+3
source share

Unfortunately std::initializer_list does not work with move semantics .

If you need to provide arguments to the constructor, I would instead use emplace_back , which builds the element in place:

 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; } 
+2
source share

All Articles