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

时间:2013-05-16 19:10:53

标签: c++ exception-handling gotw

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

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

原因是当我们new T1new T2时,可能会有类的构造函数抛出异常。

同时,根据解释:

  

简要概述:简单地称之为“新T1”这样的表达式,a   新的表达。回想一下new-expression真正做的事情(我会忽略   放置和数组形式简单,因为它们不是很好   相关):

     
      
  • 分配内存

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

  •   
  • 如果由于异常而导致构造失败,则释放已分配的内存

  •   
     

因此每个new-expression实际上是一系列两个函数调用:   一次调用operator new()(全局一个,或者一个提供者)   正在创建的对象的类型),然后调用   构造

     

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

     

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

     

问题在于:如果步骤3或步骤4因为a而失败   例外,C ++标准不要求T1对象   被毁坏,其记忆被解除分配。这是经典的内存泄漏,   显然不是一件好事。   [...]

阅读更多内容:

  

为什么标准不能通过要求编译器在清理方面做正确的事来阻止问题?

     

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

所以我的问题是:

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

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

  3. 感谢您的时间和帮助。

3 个答案:

答案 0 :(得分: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()构建{}的多种方式存在问题,但没有什么是完美的。

答案 1 :(得分: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)但这是另一回事)。

答案 2 :(得分: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>());