使用std :: uninitialized_copy的Stroustrup示例中的资源泄漏?

Dan*_*lli 9 c++

在Stroustrup一书(The C++ programming language 4th ed.,§17.5.1,pag 508)中,我找到了以下一个简单Matrix类的复制构造函数示例:

template < class T >
Matrix:: Matrix( const Matrix& m ) // copy constructor
    : dim{ m.dim },
      elem{ new T[ m.size() ] }
{
    uninitialized_copy( m.elem, m.elem+m.size(), elem ); // copy elements
}
Run Code Online (Sandbox Code Playgroud)

(elem指向T元素数组的指针,声明为T* elem;).

我有两个关于这个拷贝构造函数的问题:

  1. 为什么方法首先默认构造一个m.size()元素数组,只是用uninitialized_copy调用在体内覆盖它?

  2. 在初始化时elem{ new T[ m.size() ] },T的构造函数被称为m.size()times.但是,uninitialized_copy在同一区域中构造新数组之前,正文中的算法不会调用T析构函数.这是潜在的资源泄漏吗?(注意:不是内存泄漏,资源泄漏!例如,如果T获取ctor中的锁或文件描述符并在dtor中释放它).

谢谢

Dan*_*lli 6

我是这个问题的作者.
我通过电子邮件向Bjarne Stroustrup提出了两个问题,并想在这里分享他的答案.

他好心(并且很快:-)回答:

  1. 这是一种疏忽和浪费.
  2. 是的,如果类型T持有资源,那就是泄漏

他还写道,他将修复错误.


eca*_*mur 4

是的,这显然是一个错误;代码应该使用copy而不是uninitialized_copy. 由于T通常是一个简单的类型,因此通常可以避免资源泄漏,但这仍然是一个错误。

要回答您的第一个问题,编写复制构造函数成员初始值设定项默认构造数组会更简单,因为这允许复制构造函数使用与构造函数相同的简单分配方法(n, m)。分配未初始化的内存区域并在其中构造会更有效,但这会使构造函数和析构函数变得复杂,这会偏离 Matrix 类的目的。例如,更惯用的编写方法是使用 Matrix 包装动态数组(固定大小向量)类dynarray(可能是 C++14 TS 之后)。

正如 Puppy 所指出的,如果复制构造函数的主体抛出异常,就会发生内存泄漏。无论是否使用copy或,都会发生这种情况。Stroustrup在课堂上早些时候uninitialized_copy确实说过,所以这是可以原谅的。// simplified (no error handling)

据猜测,我想说这个错误的出现方式是 Stroustrupuninitialized_copy在本书的其他地方使用的,特别是在他的向量类中。我无权访问旧版本,但我怀疑这是为了替换向量复制构造函数中的循环和 try/catch 块而添加的,并且 Matrix 在没有充分考虑的情况下进行了类似的修改。

Stroustrup 的第 4 版勘误表列表中没有显示该内容,但他可能已经知道了(勘误表页面最近更新于 2013 年 11 月)。如果您想通过电子邮件告知他此错误,您绝对应该这样做!

  • 事实并非如此——如果复制构造函数失败,则不会调用析构函数。如果复制构造函数*确实*失败(假设“elem”是“T*”),这会导致更严重的内存泄漏/异常不安全问题。 (2认同)
  • 如果 `elem` 是一个 `unique_ptr&lt;T[]&gt;`,那么它将是完全安全的。 (2认同)