#include <random>
#include <iostream>
#include <vector>
#include <sstream>
//#define BETTER_SOLUTION
//#define OK_SOLUTION
class Resource {
public:
Resource() : pResource(new int(count++)) {}
~Resource() {}
static Resource create() {
Resource *rsc = new Resource();
}
void destroy() {
std::random_device rd;
std::default_random_engine dre(rd());
std::uniform_real_distribution<> urd(0, 1);
if (urd(rd) >= 0.95) {
std::ostringstream oStream;
oStream << "Exception from dtor. Resource " << *pResource << " leaked.";
throw std::runtime_error(oStream.str().c_str());
}
std::cout << "Release Resource " << *pResource << std::endl;
delete pResource;
}
int getResource() const { return *pResource; }
private:
int *pResource;
static int count;
};
int Resource::count = 0;
class ResourceHolder {
public:
ResourceHolder() {
#ifdef BETTER_SOLUTION
released = false;
#endif
}
~ResourceHolder() {
#if defined(BETTER_SOLUTION)
if (!released) {
try {
// Release resource if the client didn't
pResource.destroy();
} catch (std::exception &e) {
// If closing fails, log that and terminate or swallow
std::cout << "Holder cought a failure: "<< e.what() << std::endl;
// Optional.
// std::abort();
}
}
#elif defined(OK_SOLUTION)
try {
pResource.destroy();
} catch (std::exception &e) {
// Make a log that something failed.
std::cout << "Holder cought a failure: "<< e.what() << std::endl;
// Optional.
// std::abort();
}
#else
pResource.destroy();
#endif
}
#ifdef BETTER_SOLUTION
void release() {// For client code
pResource.destroy();
released = true;
}
#endif
int resource() const { return pResource.getResource(); }
friend std::ostream& operator <<(std::ostream &, const ResourceHolder&);
private:
Resource pResource;
#ifdef BETTER_SOLUTION
bool released;
#endif
};
std::ostream& operator <<(std::ostream &os, const ResourceHolder& w)
{
os << "Do something on Resource " << w.resource();
return os;
}
void createResourceAndTestLeave()
{
std::vector<ResourceHolder> v(50);
for (const auto& w : v) {
std::cout << w << std::endl;
}
#ifdef BETTER_SOLUTION
for (auto& w : v) {
try {
w.release();
} catch (std::exception &e) {
std::cout << "Client caught exception: " << e.what() << std::endl;
}
}
#endif
}
int main()
{
createResourceAndTestLeave();
}
討論
C++ 並未限制 dtor 丟出例外,但是在 dtor 丟出例外有機會造成 resource leak (沒有接此例外的話會直接離開 dtor)。std::vector<ResourceHolder> v(50);
Effective C++ 書上提到,dtor 在處理這50個元件時,丟出一個例外,程式會繼續跑。若再丟出一個,程式執行會終止或 undefined behavior。
實際使用 Visual Studio 2013 與 g++測試,丟出一個 exception 未接,程式就會 abort。
在討論書本上的解法前,先想想...若不管此例外,讓此例外往上 propagate,要在哪邊接此例外是一個大問題。
直覺上,考慮每次有物件被生成與銷毀後,就於使用此物件的函式/scope 外,接此例外。例如,在 createResourceAndTestLeave(); 外面加上 try&catch。使用 Visual Studio 2013 實測,第一次例外發生時,函式未等到剩餘的 ResourceHolder 銷毀就會離開,並且由 catch 接到例外。
由以上觀察,此作法實務上並不可行,一樣會 resource leak。
解法
[法一] 在 dtor 內 catch 例外,並呼叫 abort[法二] 在 dtor 內 catch 例外,並且吞下例外
ResourceHolder::~ResourceHolder() {
try {
pResource.destroy();
}
catch (std::exception &e) {
// Make a log that something failed.
std::cout << "Holder cought a failure: " << e.what() << std::endl;
// Optional.
// std::abort();
}
}
[法三] 提供一個介面給 client code,讓 client code 自己呼叫釋放資源的函式,並且有機會自己處理此例外。除此之外, dtor 還是會檢查 client code 有無釋放資源,若沒有,再走[法一]或[法二]
ResourceHolder::~ResourceHolder() {
if (!released) {
try {
// Release resource if the client didn't
pResource.destroy();
}
catch (std::exception &e) {
// If closing fails, log that and terminate or swallow
std::cout << "Holder cought a failure: " << e.what() << std::endl;
// Optional.
// std::abort();
}
}
}
void ResourceHolder::release() {// For client code
pResource.destroy();
released = true;
}