如何使用右值在 C++11 中复制或别名对象?

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

我想知道我对这个问题的理解是否正确,如果是,如何解决。

我正在编写一个模板 class 来存储大量类型对象并对它们执行操作。问题是我的水平还很低,看了11 pages about rvalues还是没看懂。在我的模板 class 中,如果我理解正确的话,一个重载的右值复制函数会导致主函数中的代码在执行行 'cout << ex.z;' 时导致错误,因为 'test = move(ex);' 调用左值复制函数,它将调用右值复制函数,这将导致 'ex' 引用测试引用的内容和 'test' 引用 'ex' 引用的内容?

如果那是正确的,我该如何正确实现右值对象复制功能?我对能够做到这一点的主要兴趣是创建一个正确接受右值的构造函数,而不是像我的左值构造函数那样本质上只是工作,迂回方法是为了确保我理解它。

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
}

实际上,您误解了一些与右值引用的东西。

简而言之,Vec3<int> test = move(ex) 将使用单个参数调用 Vec3<int> 的移动构造函数,即对 ex.

的右值引用

明白这个,你就明白std::move()。这个模板函数所做的是采用左值引用或纯右值(纯右值,即我们称为 "rvalues" 的东西,因为它的起源很长 long 时间ago)-reference,并将其转换为 xvalue(eXpring 值,即可以从中移动数据的值)。这样的值将绑定到其类型的右值引用。

在所有这些混乱之后,简单的解决方案是实现一个移动构造函数:

Vec3(Vec3 &&vec) : x(std::move(vec.x)), y(std::move(vec.y)), z(std::move(vec.z)) {}

您也可以将同样的原则应用于赋值运算符:

void operator=(Vec3 &&vec) {
    this->x = std::move(vec.x);
    this->y = std::move(vec.y);
    this->z = std::move(vec.z);
}

重要提示:不要简单地复制粘贴最后一个代码片段! C++11 引入了 一堆复杂的术语,一开始(通常)很难掌握。如果您不理解此 post,请阅读更多 explanations/examples 直到您理解,否则您的大脑将调用 8 位 Homo Sapiens 的神经学 ABI 规范所定义的未定义行为(非图灵完备)架构,由开放式自然选择小组于公元前 75,000 年发布。

它应该是这样的:

#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;
}

我个人更喜欢使用swap成语:

#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
}

std::vector 等 STL 容器中使用 class 时,将 noexcept 添加到移动构造函数将会受益。容器将使用移动而不是复制。