我会坚持RAII 成语。
如果您避免使用“裸”资源(例如 operator new、裸指针、裸互斥体等),而是将所有内容包装到具有适当 RAII 行为的容器或类中,您将不会遇到您描述的问题,即使存在例外情况。
也就是说,不要在你的构造函数中获取裸资源。相反,创建一个本身遵循 RAII 的对象的实例。这样,即使您的构造函数失败(即创建实例的构造函数),也会调用已初始化对象的析构函数。
所以,这是不好的做法:
#include<iostream>
#include<stdexcept>
struct Bad {
Bad() {
double *x = new double;
throw(std::runtime_error("the exception was thrown"));
}
~Bad() {
delete x;
std::cout<<"My destructor was called"<<std::endl;
}
double *x;
};
int main() {
try {
Bad bad;
} catch (const std::exception &e) {
std::cout<<"We have a leak! Let's keep going!"<<std::endl;
}
std::cout<<"Here I am... with a leak..."<<std::endl;
return 0;
}
输出:
We have a leak! Let's keep going!
Here I am... with a leak...
与这种人为且愚蠢的好实现进行比较:
#include<iostream>
#include<stdexcept>
struct Resource {
Resource() {
std::cout<<"Resource acquired"<<std::endl;
}
~Resource() {
std::cout<<"Resource cleaned up"<<std::endl;
}
};
struct Good {
Good() {
std::cout<<"Acquiring resource"<<std::endl;
Resource r;
throw(std::runtime_error("the exception was thrown"));
}
~Good() {
std::cout<<"My destructor was called"<<std::endl;
}
};
int main() {
try {
Good good;
} catch (const std::exception &e) {
std::cout<<"We DO NOT have a leak! Let's keep going!"<<std::endl;
}
std::cout<<"Here I am... without a leak..."<<std::endl;
return 0;
}
输出:
Acquiring resource
Resource acquired
Resource cleaned up
We DO NOT have a leak! Let's keep going!
Here I am... without a leak...
我的观点是:尝试将所有需要释放的资源封装到自己的类中,构造函数不会抛出,析构函数正确释放资源。然后,在析构函数可能抛出的其他类上,只需创建被包装资源的实例,获取的资源包装器的析构函数将保证被清理。
以下可能是一个更好的例子:
#include<mutex>
#include<iostream>
#include<stdexcept>
// a program-wide mutex
std::mutex TheMutex;
struct Bad {
Bad() {
std::cout<<"Attempting to get the mutex"<<std::endl;
TheMutex.lock();
std::cout<<"Got it! I'll give it to you in a second..."<<std::endl;
throw(std::runtime_error("Ooops, I threw!"));
// will never get here...
TheMutex.unlock();
std::cout<<"There you go! I released the mutex!"<<std::endl;
}
};
struct ScopedLock {
ScopedLock(std::mutex& mutex)
:m_mutex(&mutex) {
std::cout<<"Attempting to get the mutex"<<std::endl;
m_mutex->lock();
std::cout<<"Got it! I'll give it to you in a second..."<<std::endl;
}
~ScopedLock() {
m_mutex->unlock();
std::cout<<"There you go! I released the mutex!"<<std::endl;
}
std::mutex* m_mutex;
};
struct Good {
Good() {
ScopedLock autorelease(TheMutex);
throw(std::runtime_error("Ooops, I threw!"));
// will never get here
}
};
int main() {
std::cout<<"Create a Good instance"<<std::endl;
try {
Good g;
} catch (const std::exception& e) {
std::cout<<e.what()<<std::endl;
}
std::cout<<"Now, let's create a Bad instance"<<std::endl;
try {
Bad b;
} catch (const std::exception& e) {
std::cout<<e.what()<<std::endl;
}
std::cout<<"Now, let's create a whatever instance"<<std::endl;
try {
Good g;
} catch (const std::exception& e) {
std::cout<<e.what()<<std::endl;
}
std::cout<<"I am here despite the deadlock..."<<std::endl;
return 0;
}
输出(用gcc 4.8.1 编译,使用-std=c++11):
Create a Good instance
Attempting to get the mutex
Got it! I'll give it to you in a second...
There you go! I released the mutex!
Ooops, I threw!
Now, let's create a Bad instance
Attempting to get the mutex
Got it! I'll give it to you in a second...
Ooops, I threw!
Now, let's create a whatever instance
Attempting to get the mutex
现在,请不要按照我的示例创建自己的范围保护。 C++(特别是 C++11)在设计时考虑了 RAII,并提供了丰富的生命周期管理器。例如,std::fstream 将自动关闭,[std::lock_guard][2] 将执行我在示例中尝试执行的操作,std::unique_ptr 或 std::shared_ptr 将负责销毁。
最好的建议?阅读 RAII(并根据它进行设计),使用标准库,不要创建裸资源,并熟悉 Herb Sutter 关于“异常安全”的说法(继续阅读他的website,或谷歌“Herb Sutter Exception Safety”)