【问题标题】:How do I copy or alias an object in C++11 using rvalues?如何使用右值在 C++11 中复制或别名对象?
【发布时间】:2015-08-22 06:28:18
【问题描述】:

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

我正在编写一个模板类来存储许多类型对象并对它们执行操作。问题是我的技能仍然很差,所以即使在阅读11 pages about rvalues 之后我也不完全明白。在我的模板类中,如果我理解正确,一个重载的右值复制函数将使它成为主函数中的代码,当行'cout

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

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 test = move(ex);不会触发 operator=,它会触发复制构造函数。你应该有右值复制构造函数而不是 operator=.
  • 复制构造函数是什么样的? 'Vec3(Vec3 &&vec){/*类似于 this=vec?*/}'
  • 除非x、y 和z 有什么特别之处,否则最好让编译器为您生成复制和移动函数。 (除非您使用不生成移动功能的 Visual Studio 2013)

标签: c++ c++11 rvalue-reference


【解决方案1】:

它应该是这样的:

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

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

【讨论】:

  • 谢谢,但是,如果在执行该代码之后,我会调用 'test = Vec3(0,0,3);'它会崩溃,因为实现右值的重载 '=' 运算符做了一些奇怪的事情。另外,由于您试图让它使用构造函数,它不应该使用 forward 吗?我可以通过手动输入 'x = move(other.x)' 等等来让它工作。
  • 更正了代码。第一个版本递归调用移动赋值运算符。
  • 这很愚蠢。对于这个类,所有特殊的成员函数都可以而且应该只是显式地默认。手写它们很容易出错并且很悲观,因为它们不再是微不足道的。 swap 也不是实现移动构造或移动赋值的好方法,这在您的代码中尤其糟糕,因为 1) 您的移动构造函数将读取未初始化的值,导致 UB; 2) 你的会员swap 打错了swap。
  • 这个类是有问题的。问题是 - 如何为这个类编写移动构造函数。所以它就在这里。当然对于这么简单的类,所有这些特殊的方法都不需要显式定义,隐式生成就足够了。至于交换成语,只是你认为它不好,你最好在做出这样的陈述时写下恕我直言。
  • 你是对的,这个例子中的交换移动构造函数会导致UB,我的错。当类成员在声明时进行初始化时,我使用交换。交换方法有什么问题?
【解决方案2】:

实际上,您误解了与 rvalue-references 无关的东西。

简单来说,Vec3&lt;int&gt; test = move(ex) 将使用单个参数调用Vec3&lt;int&gt; 的移动构造函数,即对ex 的右值引用。

要理解这一点,你应该理解std::move()。这个模板函数所做的是获取左值引用或纯右值(纯右值,即我们称之为“右值”的东西,因为它的起源很久很久以前,很久)-引用,并将其转换为 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);
}

重要提示:不要简单地复制粘贴最后的代码 sn-p! C++11 引入了一大堆复杂的术语(通常)一开始很难掌握。如果您不理解这篇文章,请阅读更多解释/示例,直到您理解为止,否则您的大脑将调用 8 位 Homo Sapiens 的神经学 ABI 规范定义的未定义行为(非图灵-完整的)建筑,由开放自然选择小组在公元前 75,000 年出版。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-08-31
    • 2012-04-09
    • 1970-01-01
    • 2013-08-07
    • 2017-04-17
    • 2015-03-24
    相关资源
    最近更新 更多