【问题标题】:Is there a memory leak in this C++ move constructor?这个 C++ 移动构造函数中是否存在内存泄漏?
【发布时间】:2016-07-21 16:24:24
【问题描述】:

在 Stroustrups The C++ Programming Language Fourth Edition,第 76 页,有使用移动构造函数的示例。

类是这样定义的:

class Vector { 
private: 
  double∗ elem; // elem points to an array of sz doubles 
  int sz; 
public: 
  Vector(int s) :elem{new double[s]}, sz{s} 
  { for (int i=0; i!=s; ++i) elem[i]=0; // initialize elements } 

  ~Vector() { delete[] elem; } // destructor: release resources 
  Vector(const Vector& a); // copy constructor 
  Vector& operator=(const Vector& a); // copy assignment 
  Vector(Vector&& a); // move constructor 
  Vector& operator=(Vector&& a); // move assignment 

  double& operator[](int i); 
  const double& operator[](int i) const; 
  int size() const; 
}; 

定义了移动构造函数:

Vector::Vector(Vector&& a) :elem{a.elem}, // "grab the elements" from a 
  sz{a.sz} 
{ 
  a.elem = nullptr; // now a has no elements 
  a.sz = 0; 
} 

我认为会导致内存泄漏的示例执行:

Vector f() 
{ 
  Vector x(1000); 
  Vector y(1000); 
  Vector z(1000); 
  // ... 
  z=x; //we get a copy 
  y = std::move(x); // we get a move 
  // ... 
  return z; //we get a move 
}; 

这样的移动操作似乎会导致内存泄漏,因为y已经分配了1000个元素

Vector y(1000); 

在第 y 行进行简单的指针重新分配 = std::move(x);会留下这些由 y 指向的初始 1000 个整数。我假设,移动构造函数必须有额外的代码行才能在移动之前取消分配指针'elem'。

【问题讨论】:

  • 您展示的是移动复制构造函数。移动分配是如何实现的,因为这就是您的示例中调用的内容。
  • 无关,我觉得你移动 elem 成员很奇怪,但似乎忽略了移动 sz 成员。如果将移动源归零足够重要,那么保留移动目标可能足够重要。在该成员初始化程序之后有一个悬空的,,所以也许您只是忽略了为该构造函数粘贴 all 代码(无论出于何种原因)。
  • @WhozCraig 看起来确实如此,但 OP 使用了评论并破坏了格式。看看elem{a.elem}, // "grab the elements" from a z{a.sz}
  • @NathanOliver 优秀。
  • 是的,我更正了最初错误粘贴的代码,请参见上面的代码。

标签: c++ memory-leaks move-constructor


【解决方案1】:

假设正确实现了移动构造函数,不。您引用的行调用移动赋值,而不是构造函数,因为z 已经存在。

【讨论】:

  • 是的,同意,这是移动赋值:y = std::move(x);,而不是构造函数。在书中他们没有展示它,只是提到移动分配的定义类似。但是,据我所知,在移动分配定义中,我们需要释放 elem,对吗?像这样的东西? Vector& Vector::operator=(Vector&& a) { delete[] elem; elem = a.elem; sz = a.sz; a.elem = nullptr; // 现在 a 没有元素 a.sz = 0; }
  • @AndreyChertov 要么这样,要么将您的elem ptr 和sz 与另一个Vector 交换,获取它的数据并让它自己处理。如果您对移动分配的实施有疑问,请用它更新您的问题——我不拥有这本书。
  • 不错!在移动分配中,交换看起来比删除 [] 更优雅。好决定。这样,每个对象在超出范围时都会自行处理。在这种情况下,不应使用 nullptr。
【解决方案2】:

此 C++ 移动构造函数中是否存在内存泄漏?

没有。

我认为会导致内存泄漏的示例执行:

显示的执行调用移动赋值,而不是移动构造函数。

我假设,移动构造函数在移动之前必须有额外的代码行来解除指针'elem'的分配。

不需要,因为新构造的对象的指针在使用 move 参数中的指针值初始化之前不能指向任何分配的内存。

【讨论】:

    【解决方案3】:

    是的,同意,这是移动赋值:y = std::move(x);,而不是构造函数。 在书中他们没有展示它,只是提到移动分配的定义类似。 但是,据我所知,在移动分配定义中,我们需要解除分配 elem,对吗?像这样。

    Vector& Vector::operator=(Vector&& a)
    { 
      delete[] elem;
      elem = a.elem;
      sz = a.sz;
      a.elem = nullptr; // now a has no elements 
      a.sz = 0; 
    }
    

    【讨论】:

      【解决方案4】:

      在第 y 行进行简单的指针重新分配 = std::move(x);会离开 y 指向的这些最初的 1000 个整数是自己留下的。

      指向 double 的指针的地址被复制到新对象 (y) 和旧对象 (x) 的指针设置为 nullptr。 (发生在未显示的移动分配方法中)

      我假设,移动构造函数必须有额外的代码行 在移动之前取消分配指针'elem'。

      这里不需要解除分配。指针只是在分配期间“移动”(它也将在移动构造期间移动)。双指针的清理应由析构函数处理。在 x 的情况下,析构函数最终会在 nullptr 上触发。 nullptr 上的 delete[] 是一个 noop。见http://www.cplusplus.com/reference/new/operator%20delete[]/

      然而,正如所指出的,在移动分配期间需要清除在 y 中分配的双打。

      【讨论】:

      • "指针在赋值过程中只是被‘移动’了……双指针的清理应该由析构函数来处理。”如果我们在移动分配中不清理,最初分配的 1000 初始 y 将泄漏。析构函数将仅删除 [] 最初分配在 x 中的 1000 个并移至 y。但是在 y 中分配的需要在 elem = a.elem; 之前删除[]
      • 啊,现在。好决定。是的,Y中的需要在转移前清除。
      猜你喜欢
      • 1970-01-01
      • 2014-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-17
      • 2013-01-12
      • 2022-11-04
      • 1970-01-01
      相关资源
      最近更新 更多