Search code examples
c++c++11rvalue-reference

How do I copy or alias an object in C++11 using rvalues?


I want to know if my understanding of the problem is correct, and if so, how it might be solved.

I'm programming a template class to store a number of type objects and perform operations on them. The problem is that my skills are still inferior so even after reading 11 pages about rvalues I don't totally get it. In my template class, if I understand correctly, an overloaded rvalue copy function would make it so that the code in the main function would result in an error when the line 'cout << ex.z;' is executed because instead of 'test = move(ex);' calling the lvalue copy function it would call the rvalue one which would result in 'ex' referencing what test referenced and 'test' referencing what 'ex' referenced?

If that is correct, how do I properly implement the rvalue object copy function? My primary interest in being able to do this is to create a constructor that takes an rvalue properly instead of essentially just working like my lvalue constructor does and the roundabout method is to make sure I understand it.

template<class T>
class Vec3
{
public:
    Vec3(){}
    Vec3(const Vec3 &vec):x(vec.x),y(vec.y),z(vec.z){}

    void operator = (const Vec3 &other)
    {x=other.x; y=other.y; z=other.z;}

//    void operator = (Vec3 &&other)
//    {
//this would just call the other overloaded copy function
//        *this = move(other);
//    }

    T x, y, z;
};

main(){
  Vec3<int> ex(0,0,0);
  Vec3<int> test = move(ex);
  test.z++;
  cout << test.z;//will be 1
  cout << ex.z;//will be 0
}

Solution

  • This is how it should look like:

    #include <iostream>
    
    template<class T>
    class Vec3
    {
    public:
        Vec3() {}
        Vec3(T x_, T y_, T z_): x(x_), y(y_), z(z_)
        {}
        Vec3(const Vec3 &vec):x(vec.x),y(vec.y),z(vec.z)
        {}
        Vec3(Vec3 &&vec) noexcept : x(std::move(vec.x)),y(std::move(vec.y)),z(std::move(vec.z))
        {}
        Vec3& operator=(const Vec3 &other)
        {
            x=other.x; y=other.y; z=other.z;
            return *this;
        }
        Vec3& operator=(Vec3 &&other) noexcept
        {
            x = std::move(other.x);
            y = std::move(other.y);
            z = std::move(other.z);
            return *this;
        }
    
        T x, y, z;
    };
    
    int main(){
      Vec3<int> ex(0,0,0);
      Vec3<int> ex1(1,1,1);
      ex1 = std::move(ex); // <-- this will call move assignment operator
      Vec3<int> test = std::move(ex); // <-- this will call move constructor
      test.z++;
      std::cout << test.z;//will be 1
      std::cout << ex.z;//will be 0
      return 0;
    }
    

    I personally prefer using swap idiom:

    #include <iostream>
    
    template<class T>
    class Vec3
    {
    public:
        Vec3() {}
        Vec3(T x_, T y_, T z_): x(x_), y(y_), z(z_)
        {}
        Vec3(const Vec3 &vec):x(vec.x),y(vec.y),z(vec.z)
        {}
        Vec3(Vec3 &&other) noexcept
        { swap(other); }
        Vec3& operator=(const Vec3 &other)
        {
            x=other.x; y=other.y; z=other.z;
            return *this;
        }
        Vec3& operator=(Vec3 &&other) noexcept
        {
            Vec3{std::move(other)}.swap(*this);
            return *this;
        }
        void swap(Vec3 &other) noexcept
        {
            std::swap(x, other.x);
            std::swap(y, other.y);
            std::swap(z, other.z);
        }
        T x, y, z;
    };
    
    main(){
      Vec3<int> ex(0,0,0);
      Vec3<int> ex1(1,1,1);
      ex1 = std::move(ex); // <-- this will call move assignment operator
      Vec3<int> test = std::move(ex); // <-- this will call move constructor
      test.z++;
      std::cout << test.z;//will be 1
      std::cout << ex.z;//will be 0
    }
    

    Adding noexcept to your move constructor will benefit, when using your class in STL containers like std::vector etc. Container will use move instead of copy.