1

根据GOTW #56,以下代码中存在潜在的经典内存泄漏和异常安全问题:

//  In some header file:
void f( T1*, T2* );
//  In some implementation file:
f( new T1, new T2 );

原因是当 wenew T1或时new T2,类的构造函数可能会抛出异常。

同时,根据解释:

简要回顾:简单地说,像“new T1”这样的表达式称为 new-expression。回想一下 new-expression 的真正作用(为简单起见,我将忽略放置和数组形式,因为它们在这里不是很相关):

  • 它分配内存

  • 它在该内存中构造一个新对象

  • 如果构造因异常而失败,则释放分配的内存

因此,每个 new 表达式本质上是一系列两个函数调用:一个对 operator new() 的调用(要么是全局的,要么是由正在创建的对象的类型提供的),然后是对构造函数的调用。

对于示例 1,考虑如果编译器决定生成如下代码会发生什么:

1:为 T1 分配内存
2:构造 T1
3:为 T2 分配内存
4:构造 T2
5:调用 f()

问题是这样的:如果第 3 步或第 4 步由于异常而失败,则 C++ 标准不要求销毁 T1 对象并释放其内存。这是典型的内存泄漏,显然不是一件好事。[...]

通过阅读更多:

为什么标准不通过要求编译器在清理时做正确的事情来防止问题?

基本的答案是它没有被注意到,即使现在它已经被注意到,也可能不需要修复它。C++ 标准允许编译器对表达式的求值顺序有一定的自由度,因为这允许编译器执行原本不可能的优化。为实现这一点,表达式评估规则以非异常安全的方式指定,因此如果您想编写异常安全代码,您需要了解并避免这些情况。(请参阅下文了解如何最好地做到这一点。)

所以我的问题是:

  1. 如何修复这个典型的异常不安全代码?我们应该避免编写这样的代码吗?

  2. 答案让我有点困惑,为了处理构造函数失败,我们应该根据C++ FAQ从构造函数中抛出异常并确保分配的内存被正确释放,所以假设类 T 确实实现了处理构造失败的代码,我们是否上面的代码中还有异常安全问题吗?

感谢您的时间和帮助。

4

3 回答 3

3

首先,写make_unique

template<typename T, typename... Args>
std::unique_ptr<T> make_unique( Args&&... args ) {
  return {new T(std::forward<Args>(args)...)};
}

这未包含在标准中,基本上是作为疏忽。 std::make_shared是,并且make_unique可能会出现在 C++14 或 17 中。

其次,将您的函数签名更改为:

//  In some header file:
void f( std::unique_ptr<T1>, std::unique_ptr<T2> );

并称之为:

f( make_unique<T1>(), make_unique<T2>() );

结果是异常安全的代码。

如果T1您想使用一个重要的构造函数,您只需将参数传递给make_unique<T1>它,它就会完美地将它们转发给T1.

T1构建通孔()或的多种方式存在问题{},但没有什么是完美的。

于 2013-05-16T19:25:46.043 回答
2

如何修复这个典型的异常不安全代码?

使用智能指针。如果您的函数采用裸指针并且您无法控制它,您可以这样做:

std::unique_ptr<T1> t1(new T1);
std::unique_ptr<T2> t2(new T2);
// assuming f takes ownership of the pointers:
f(t1.release(), t2.release());

假设类 T 确实实现了处理构造失败的代码,我们在上面的代码中是否仍然存在异常安全问题?

这里异常安全与构造函数是否可以抛出几乎没有关系:new它本身可以抛出,因为它无法分配内存,我们回到第一个问题。因此,当使用 分配对象时new,应该始终假设构造可能会失败,即使构造函数明确声明它不能抛出(当然,使用时除外,new(std::nothrow)但这是另一回事)。

于 2013-05-16T19:25:31.083 回答
2

第一:是的,避免线程安全问题。

如果你不能重写f,考虑这个:

auto t1 = std::make_unique<T1>(); //C++14
std::unique_ptr<T2> t2{new T2}; //C++11
f( t1.get(), t2.get() );        //or release(), depending on ownership policies of f

但是,如果可以,请执行以下操作:

void f(std::unique_ptr<T1>, std::unique_ptr<T2>);

//call:
f(make_unique<T1>(), make_unique<T2>());
于 2013-05-16T19:45:18.470 回答