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 a B
, I always have an optimal forward path for A
, one move for rvalues or one copy for lvalues.
Is it possible to achieve the same result with one constructor? It's not a big problem in this case, but what about multiple parameters? I would need combinations of every possible occurrence 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 the copy/move calls gets executed.
Parameter passing involves passing input parameters into a module (a function in C and a function and procedure in Pascal) and receiving output parameters back from the module. For example a quadratic equation module requires three parameters to be passed to it, these would be a, b and c.
Parameter Passing in C++ There are three parameter-passing modes in C++: by value, by pointer, and by reference.
Formal Parameter : A variable and its type as they appear in the prototype of the function or method. Actual Parameter : The variable or expression corresponding to a formal parameter that appears in the function or method call in the calling environment. Modes: IN: Passes info from caller to callee.
Pass-by-references is more efficient than pass-by-value, because it does not copy the arguments. The formal parameter is an alias for the argument. When the called function read or write the formal parameter, it is actually read or write the argument itself.
The "by-value" approach is an option. It is not as optimal as what you have, but only requires one overload:
class B
{
A a;
public:
B (A _a) : a(move(_a)) {}
};
The cost is 1 extra move construction for both lvalues and xvalues, but this is still optimal for prvalues (1 move). An "xvalue" is an lvalue that has been cast to rvalue using std::move.
You could also try a "perfect forwarding" solution:
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 get you back to the optimal number of copy/move constructions. But you should constrain the template constructor such that it is not overly generic. You might prefer to use is_convertible instead of is_constructible as I've done above. This is also a single constructor solution, but as you add parameters, your constraint gets increasingly complicated.
Note: The reason the constraint is necessary above is because without, clients of B
will get the wrong answer when they query std::is_constructible<B, their_type>::value
. It will mistakenly answer true without a proper constraint on B
.
I would say that none of these solutions is always better than the others. There are engineering tradeoffs to be made here.
Use a deduced parameter type 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 an A
object is constructible.
If A
has multiple constructors with a varying number of arguments, you can just make the whole thing variadic by adding ...
everywhere.
As @Howard says, though, you should add a constraint so that the class doesn't appear to be constructible from arguments from which it really isn't.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With