Reputation: 7809
Suppose I have a copy constructor. This constructor calls a hierarchy of functions, passing the copied object as an lvalue reference.
Now, I also have a move constructor which basically could use the same function hierarchy as the copy constructor. This would work, since I can pass the rvalue argument to the lvalue hierarchy.
BUT somewhere down in the hierarchy, I have a function which would copy a resource in the lvalue case and 'steal' the resource in the rvalue case.
Is there a way to decide, whether the lvalue reference passed to that function originates from an rvalue? I guess not. Or what is the common approach, when you have a hierarchy of functions for copying which you could use for copy and move constructions and which only differ in very few functions?
Code sample:
class A{
A(const A& a){
initFrom(a);
}
A(A&& a){
initFrom(a);
}
void initFrom(const A& a){
// call a hierarchy of functions, of which one of them calls initResource(const A&)
}
void initResource(const A& a){
if(a == rvalue reference){ // **** Here's the question... ****
// steal resource
this->ptr = a.ptr;
a.ptr = nullptr;
}
else{
// copy resource
this->ptr = allocate...
copy from a.ptr to this->ptr
}
}
Upvotes: 0
Views: 116
Reputation: 2241
Depending on your call hierarchy and what all those functions have to do except for passing down the object you might use another technique if you aim to store the object inside your class anyway.
class A {
A(const A& a) {
initFrom(A(a)); // take a copy here
}
A(A&& a) {
initFrom(std::move(a)); // move here
}
void initFrom(A&& a) {
initResource(std::move(a)); // just pass down
}
void initResource(A&& a) {
// you already have your copy of a here that you can store completely
// or take its guts
}
This way you only have to implement all the methods once (for an rvalue reference) and whether moving or taking a copy is handled directly inside the method call. Take care that you always have to std::move() to pass down the rvalue reference.
Upvotes: 1
Reputation: 477090
This is a typical example for perfect forwarding:
template <typename T>
A(T && t) { initFrom(std::forward<T>(a)); }
template <typename T>
void initFrom(T && t)
{
// other calls
initResource(std::forward<T>(t));
}
void initResource(A const & rhs) { /* copy from rhs */ }
void initResource(A && rhs) { /* move from rhs */ }
(It seems that you should either be able to merge initFrom
into the constructor, or otherwise your class may be trying to do too much and you should refactor it into single-responsibility components.)
Upvotes: 2
Reputation: 30605
One alternative here is to modify the initFrom
to accept a "universal reference" to allow reference collapsing and then use std::forward
for perfect forwarding. You may then need to re-factor the remaining call hierarchy.
class A{
A(const A& a){
initFrom(a);
}
A(A&& a){
initFrom(a);
}
template <typename B>
void initFrom(B&& a){ // reference collapsing applies
// call a hierarchy of functions, of which one of them calls initResource(const A&)
initResource(std::forward<B>(a));
}
void initResource(A&& a){
// steal resource
this->ptr = a.ptr;
a.ptr = nullptr;
}
void initResource(const A& a){
// copy resource
this->ptr = allocate...
//copy from a.ptr to this->ptr
}
};
I think a simpler alternative is to first "move" the resource into you class before the initFrom
is call.
A(A&& a){
this->ptr = a.ptr;
a.ptr = nullptr;
initFrom(a);
}
But you mileage here may vary.
Upvotes: 1