Michael
Michael

Reputation: 7809

Re-using code for lvalues and rvalues

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

Answers (3)

nh_
nh_

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

Kerrek SB
Kerrek SB

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

Niall
Niall

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

Related Questions