根据GOTW #56,以下代码中存在潜在的经典内存泄漏和异常安全问题:
// In some header file:
void f( T1*, T2* );
// In some implementation file:
f( new T1, new T2 );
原因是当我们new T1
或new 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 ++标准允许编译器具有表达式评估顺序的一些纬度,因为这允许编译器执行可能无法实现的优化。为了允许这样,表达式评估规则以某种方式指定这不是异常安全的,因此如果您想编写异常安全的代码,您需要了解并避免这些情况。 (请参阅下文,了解如何做到最好。)
所以我的问题是:
如何修复这个典型的异常不安全代码?我们应该简单地避免编写这样的代码吗?
为了处理,答案让我感到困惑 构造函数失败,我们应该根据C++ FAQ从构造函数中抛出异常并确保分配的内存被正确释放,所以假设类T确实实现了处理构造失败的代码,我们在上面的代码中是否还有异常安全问题?
感谢您的时间和帮助。
答案 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>());