1

我从链接学习移动语义

我有一堂课

class Holder
{
public:

    Holder(int size)         // Constructor
    {
        m_data = new int[size];
        m_size = size;
    }

    ~Holder()                // Destructor
    {
        delete[] m_data;
    }
    Holder(const Holder& other)
    {
        cout << "copy constructor" << endl;
        m_data = new int[other.m_size];
        memcpy(m_data, other.m_data, sizeof(int));
        m_size = other.m_size;
    }
    Holder &operator=(const Holder& other)
    {
        if (this == &other)
            return *this;
        delete[]m_data;
        m_data = new int[other.m_size];
        memcpy(m_data, other.m_data, sizeof(int));
        m_size = other.m_size;
        return *this;  
    }

private:

    int*   m_data;
    size_t m_size;
};

此类具有复制构造函数,例如:

    Holder(const Holder& other)
    {
        cout << "copy constructor" << endl;
        m_data = new int[other.m_size];
        memcpy(m_data, other.m_data, sizeof(int));
        m_size = other.m_size;
    }

然后移动构造函数实现如下:

Holder(Holder&& other)     // <-- rvalue reference in input
{
  m_data = other.m_data;   // (1)
  m_size = other.m_size;
  other.m_data = nullptr;  // (2)
  other.m_size = 0;
}

我有一个问题:为什么我们不实现如下的复制构造函数:

Holder( Holder& other)
{
    m_data = other.m_data;
    m_size = other.m_size;

    other.m_data = nullptr;
    other.m_size = 0;
}

你能告诉我为什么这种方式不使用吗?谢谢

4

3 回答 3

1

您对“复制构造函数”的实现在语义上介于复制构造函数和移动构造函数之间,这将是令人困惑的,然后是危险的。如果您的意图是始终移动并且永远无法复制对象,那么您可以使用以下命令强制类不可复制:

Holder(const Holder& other) = delete;
Holder& operator=( const Holder& ) = delete;

即使您遵循编译器不生成默认复制构造函数和默认赋值的规则,显式删除这些方法也更加清晰。

于 2019-05-17T13:31:58.533 回答
1

我猜是出于安全原因。尽管您所描述的内容是允许的(并且当人们知道自己在做什么时是正确的),但是这样做最终可能会得到一个看起来很满但实际上是空的对象。

通常,移动构造函数对于将其与临时值一起使用更有用,例如:

Holder a_function(...){...}

然后可用于以下构造:

Holder object(a_function(...));

或者在执行以下操作时避免重新分配/复制大量数据/内存:

Holder object(Holder(100));

但特别是对于这种没有默认构造函数的情况(因此通常每个对象在构造后都应该是完整的),通过将复制构造函数作为您建议的内容(类似于移动构造函数),然后执行以下操作:

Holder object1(100);
Holder object2(object1);

最终会object1得到看起来像普通对象但它是空的。因此,它可能会在以后成为 bug/s 的来源。

虽然很明显,但我要补充一点,对象1的销毁不会有问题。只是,在它的生命周期中使用它,如果没有某种类型的安全防护(边界检查)不知道它是空的,很可能会导致非法内存访问。

于 2019-05-17T09:50:32.453 回答
-1

我有一个问题:为什么我们不实现如下的复制构造函数:

Holder( Holder& other)
{
    m_data = other.m_data;
    m_size = other.m_size;

    other.m_data = nullptr;
    other.m_size = 0;
}

你能告诉我为什么这种方式不使用吗?

上面的代码不是从其他复制状态,而是将其他的状态/拥有的资源移动到正在创建的当前对象。

移动构造函数

移动构造函数通常“窃取”参数持有的资源(例如,指向动态分配对象的指针、文件描述符、TCP 套接字、I/O 流、正在运行的线程等)而不是复制它们,并将参数保留在一些有效但不确定的状态。

Holder A;
Holder B(std::move(A));  
// B is created by calling move constructor    
// Resources held by A are transferred to B  (ref1)

Holder C;
Holder D(C);             
//C is created by calling copy constructor
//state or resources of C, D are same and C can be used after this
//Object C usable

参考1

于 2019-05-17T10:22:23.403 回答