【问题标题】:How do I move dynamically allocated data from an object to another?如何将动态分配的数据从一个对象移动到另一个对象?
【发布时间】:2018-03-17 10:37:04
【问题描述】:

我有一个使用 RAII 编写的 Array 类(为了本示例的目的而进行了超级简化):

struct Array
{
    Array(int size) {
        m_size = size;
        m_data = new int[m_size];
    }

    ~Array() {
        delete[] m_data;
    }

    int* m_data = nullptr;
    int  m_size = 0;
};

然后我有一个函数,它引用一个数组并对其进行一些操作。我使用一个临时数组temp 来执行处理,因为有几个原因我不能直接使用引用。完成后,我想将数据从临时数组传输到真实数组:

void function(Array& array)
{
    Array temp(array.m_size * 2);

    // do heavy processing on `temp`...

    array.m_size = temp.m_size;
    array.m_data = temp.m_data;
}

明显的问题是temp 在函数末尾超出范围:触发了它的析构函数,进而删除了内存。这样array 将包含不存在的数据。

那么,将数据所有权从一个对象“移动”到另一个对象的最佳方式是什么?

【问题讨论】:

  • “最好的方法”是完全避免使用原始指针和数组并使用std::vector
  • 或者如果你真的坚持这样做(例如:因为这是一个练习)那么你需要实现你自己的复制和移动构造函数(参见:The rule of three/five/zero

标签: c++ c++11 memory move-semantics temporary


【解决方案1】:

您想要的是移动构造或移动数组类的分配,这样可以节省冗余副本。此外,它将帮助您使用std::unique_ptr,它将为您处理分配内存的移动语义,这样您就不需要自己直接进行任何内存分配。您仍然需要挽起袖子并遵循"rule of zero/three/five",在我们的例子中是五法则(复制和移动构造函数、复制和移动赋值运算符、析构函数)。

那么,试试吧:

class Array {
public:
    Array(size_t size) : m_size(size) {
        m_data = std::make_unique<int[]>(size);
    }

    Array(const Array& other) : Array(other.size) {
        std::copy_n(other.m_data.get(), other.m_size, m_data.get());
    }

    Array(Array&& other) : m_data(nullptr) {
        *this = other;
    } 

    Array& operator=(Array&& other) {
        std::swap(m_data, other.m_data);
        std::swap(m_size,other.m_size);
        return *this;
    }

    Array& operator=(const Array& other) {
        m_data = std::make_unique<int[]>(other.m_size);
        std::copy_n(other.m_data.get(), other.m_size, m_data.get());
        return *this;
    }

    ~Array() = default;

    std::unique_ptr<int[]>  m_data;
    size_t                  m_size;
};

(实际上,将m_sizem_data 公开是一个坏主意,因为它们是捆绑在一起的,您不希望人们与它们混淆。我还将在元素类型上对该类进行模板化,即T 代替 int 并使用 Array&lt;int&gt;)

现在您可以以非常直接的方式实现您的功能:

void function(Array& array)
{
    Array temp { array }; // this uses the copy constructor

    // do heavy processing on `temp`...

    std::swap(array, temp); // a regular assignment would copy
}

【讨论】:

  • 看起来不错。您没有直接使用复制/移动 ctor/移动赋值运算符/...,所以我猜 std::swap 需要它们才能工作,对吧?
  • 我认为你应该为你的 unique_ptr 定义删除器应该delete[] ptr
  • @mahdi_12167:IIRC make_unique 的数组版本负责处理。
  • 如果 other 被视为 r-value,你为什么要做...std::move(other.m_data));?你真的需要指定 std::move 吗?。
  • 最后一个问题,赋值运算符方法也应该返回*this,不是吗?谢谢。
【解决方案2】:

如果您坚持使用示例格式,那么您会遗漏一些东西。您需要确保临时数组不会破坏非临时数组重用的内存。

 ~Array() {
    if (md_data != nullptr) 
         delete [] m_data;
 }

并且函数需要做干净的移动

void function(Array& array)
{
    Array temp(array.m_size * 2);

    // do heavy processing on `temp`...

    array.m_size = temp.m_size;
    array.m_data = temp.m_data;
    temp.m_data = nullptr;
    temp.m_size = 0;
}

为了让它更像 c++,你可以做很多事情。即在数组中,您可以创建成员函数以从其他数组(或构造函数,或分配运算符,...)中移动数据

  void Array::move(Array &temp) {
      m_size = temp.m_size;
      temp.m_size = 0;
      m_data = temp.m_data;
      temp.m_data = 0;
  }

在 c++11 及更高版本中,您可以创建移动构造函数并从函数中返回您的值:

 Array(Array &&temp) {
      do your move here
 }
 Array function() {
      Array temp(...);
      return temp;
 }

还有其他方法。

【讨论】:

  • if (md_ata != nullptr) 是不必要的,在空指针上调用 delete[] 不会发生任何坏事
  • 另外,(N)RVO 不需要移动构造函数
  • 在需要时支持较少的 C++11-ish 方式
猜你喜欢
  • 1970-01-01
  • 2012-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-05
  • 2018-04-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多