2013-05-16 81 views
1

GOTW #56,有下面的代码中潜在的经典内存泄漏和异常的安全问题:如何解决这个典型的异常不安全代码?

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

的原因是,当我们new T1,或new T2,有可能是例外从班构造抛出。

同时,根据该解释:

简要回顾一下:如“新T1”的表达式称为,只需足够的,一个新 表达。回顾一下新的表达确实(我会忽略简单 布局和排列形式,因为他们不是很 与此有关):

  • 分配内存

  • 它构造了一个在存储新对象

  • 如果因为异常的分配的内存被释放的构造失败

因此,每个新的表达基本上是一个系列的两个函数调用:) 一个调用操作者新的((无论是全球性的,或一个由 正在创建的对象的类型提供),然后到一个呼叫 构造函数。

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

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

问题是这样的:如果任一步骤3或步骤4中失败,因为一个 异常,C++标准并不要求的T1对象是 被销毁并且其内存释放。这是一个经典的内存泄漏, 和显然不是一件好事。 [...]

通过阅读更多:

为什么不标准只是防止这个问题通过要求编译器做正确的事情,当它来清理?

最基本的答案是,它没有注意到,甚至现在,它已经注意到它可能是不希望解决它。 C++标准允许编译一些宽容度表现的评价的顺序,因为这允许进行优化,可能否则是不可能的编译器。要允许此,表达评价规则的方式,是不是异常安全规定,所以如果你想写你需要了解和避免这些情况的异常安全的代码。 (请参阅下面的最佳做法。)

所以我的问题是:

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

  2. 答案弄得我一点点,为了处理 构造失败,我们应根据C++ FAQ抛出从构造异常,并分配都被正确释放一定的内存,所以假设T类没有实现代码处理建设失败,我们在上面的代码中是否还有异常安全问题?

谢谢你的时间和帮助。

+0

*如何解决这个典型的异常不安全的代码?我们应该简单地避免编写这样的代码吗? - 这篇文章(写在2009年,顺便说) - “在您穿过公司代码档案的尘土飞扬的角落时,您会发现以下代码片段......”。简单地说 - 你不应该写这样的代码。 – SChepurin

回答

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的多种方式存在问题,但没有什么是完美的。

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),但这是另一回事),应始终假定构造可能失败。

+0

如果'f'拥有指针的所有权,为什么它应该有'void(T1 *,T2 *)'的签名而不是void(std :: unique_ptr ,std :: unique_ptr ) '? – Yakk

+0

@Yakk:遗留代码。无论如何,如果'f'不拥有指针的所有权,那么即使没有异常,原始代码总是会泄漏,所以我们只能假设不在函数签名中使用'unique_ptr'的好理由是因为你不能修改该功能。 ;) – syam

+0

这是一个很好的理由吗?如果你有遗留的代码,只需弃用'f'(通过任何机制),并创建一个'f' that_does_not_suck,它需要'std :: unique_ptr's,并且只使用它。我想如果你的工具链中缺少任何重构/弃用工具...... – Yakk

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>());