C ++ 11 optimal parameter transition

Consider these classes:

#include <iostream> #include <string> class A { std::string test; public: A (std::string t) : test(std::move(t)) {} A (const A & other) { *this = other; } A (A && other) { *this = std::move(other); } A & operator = (const A & other) { std::cerr<<"copying A"<<std::endl; test = other.test; return *this; } A & operator = (A && other) { std::cerr<<"move A"<<std::endl; test = other.test; return *this; } }; class B { A a; public: B (A && a) : a(std::move(a)) {} B (A const & a) : a(a) {} }; 

When creating B , I always have the best forward path for A , one step for rvalues, or one instance for lvalues.

Is it possible to achieve the same result with a single constructor? This is not a big problem in this case, but what about a few parameters? I need combinations of all possible occurrences of lvalues โ€‹โ€‹and rvalues โ€‹โ€‹in the parameter list.

This is not limited to constructors, but also applies to function parameters (e.g., setters).

Note. This question is strictly about class B ; class A exists only to visualize how copy / move calls are made.

+7
source share
3 answers

The โ€œby valueโ€ approach is an option. This is not as optimal as yours, but only one overload is required:

 class B { A a; public: B (A _a) : a(move(_a)) {} }; 

The cost of 1 extra move construct for lvalues โ€‹โ€‹and xvalues, but this is still optimal for prvalues โ€‹โ€‹(1 turn). "Xvalue" is the lvalue value that was assigned to rvalue using std :: move.

You can also try "perfect call forwarding":

 class B { A a; public: template <class T, class = typename std::enable_if < std::is_constructible<A, T>::value >::type> B (T&& _a) : a(std::forward<T>(_a)) {} }; 

This will return you to the optimum number of copy / move designs. But you must restrict the template constructor so that it is not too general. You may prefer to use is_convertible instead of is_constructible, as I did above. It is also a solution for a single constructor, but as you add parameters, your restriction becomes more complex.

Note. The reason this restriction is required above is because without them, clients B will receive the wrong answer when they request std::is_constructible<B, their_type>::value . He will mistakenly answer the truth without proper restriction on B

I would say that none of these solutions are always better than others. Technical compromises must be made here.

+9
source

Use the type of the output parameter for the constructor for B :

 template <typename T> explicit B(T && x) : a(std::forward<T>(x) { } 

This will work for any argument from which to construct an object A

If A has several constructors with a different number of arguments, you can just make it all variable by adding ... everywhere.

As @Howard says, you should add a constraint so that the class does not seem constructive from the arguments, of which it really is not.

+2
source

If the string in your example is std::string , itโ€™s just the same: by default, a copy and forwarding of their respective members is provided. And std::string has a copy and moves both implemented, so that temporary moves move, variables are copied.

There is no need to define a specific copy and move ctor and assign. You can just leave the constructor

 A::A(string s) :test(std::move(s)) {} 

In general, a simple copy and move implementation may be as follows

 class A { public: A() :p() {} A(const A& a) :p(new data(*ap)) {} //copy A(A&& a) :p(ap) { ap=0; } //move A& operator=(A a) //note: pass by value { clear(); swap(a); return *this; } ~A() { clear(); } void swap(A& a) { std::swap(p,ap); } void clear() { delete p; p=0; } private: data* p; }; 

operator= takes a value that is internally moved. If it comes from a temporary move, if it comes from a variable, it is copied. The difference between copy and move requires different constructors, but if we get A as

 class B: public A { ... }; 

there is no need to redefine anything, because by default copy-ctor for B calls a copy for A, and by default for B causes a move for A, and all the default assignment operators for B call the only one defined for A (which is moved or copied depending from what was redirected).

+1
source

All Articles