Skip to content

Instantly share code, notes, and snippets.

@cbsmith
Created August 8, 2012 23:50
Show Gist options
  • Save cbsmith/3299796 to your computer and use it in GitHub Desktop.
Save cbsmith/3299796 to your computer and use it in GitHub Desktop.
The problem with std::uncaught_exception illustrated
// -*- compile-command: "clang++ -ggdb -o dont_terminate -std=c++0x -stdlib=libc++ dont_terminate.cpp" -*-
// Demonstration of the problem that can happen with using std::uncaught_exception() to determine
// whether it is safe to throw from within a destructor
// Problem identified, as always, by GOTW: http://www.gotw.ca/gotw/047.htm
#include <stdexcept>
#include <iostream>
#include <cassert>
using namespace std;
[[noreturn]] void terminate_called() {
cerr << "This is undesirable: terminate handler called" << endl;
abort();
}
template <typename T>
void throw_if_safe(T e) {
//look ma, throwing an exception, never triggering terminate()!
if (!uncaught_exception()) {
throw e;
}
}
struct dont_call_terminate {
~dont_call_terminate() noexcept(false) {
throw_if_safe(runtime_error("Let's throw an exception"));
}
static const char* name;
};
const char* dont_call_terminate::name = "dont_call_terminate";
struct throw_when_destruct {
[[noreturn]] ~throw_when_destruct() noexcept(false) {
throw runtime_error("Throwing without a care in the world");
}
static const char* name;
};
const char* throw_when_destruct::name = "throw_when_descruct";
template <typename T>
void sanity_test() {
bool caught_exception = false;
try {
T foo;
} catch (exception& e) {
caught_exception = true;
}
assert(caught_exception);
}
void good_part() {
sanity_test<dont_call_terminate>();
sanity_test<throw_when_destruct>();
bool caught_logic_error = false;
//if we used throw_of_desctruction here, terminate() would be called
try {
dont_call_terminate bar; //this should not throw
throw logic_error("foobar"); //because of this
} catch (logic_error& e) {
caught_logic_error = true;
} catch (runtime_error& e) {
//THIS SHOULD NEVER HAPPEN
assert(false);
}
assert(caught_logic_error);
}
//This is the problem
template <typename T>
struct ruhroh {
ruhroh(bool& aFlag)
: flag(aFlag) {}
~ruhroh() {
try {
T trouble;
//since we're in a try block that will catch runtime_exception,
//it is actually safe for foo::~foo() to throw, even if there
//is an uncaught exception unwinding the stack, but
//~dont_call_terminate() has no way of knowing that
} catch (exception e) {
flag = !flag;
}
}
private:
bool& flag;
};
template <typename T, bool destruct_while_unwinding>
void ruhroh_t() {
bool have_scooby_snacks = true; //no reason shaggy can't always get these
bool caught_logic_error = false;
try {
ruhroh<T> shaggy(have_scooby_snacks);
if (destruct_while_unwinding) {
throw logic_error("I'm just making trouble");
}
} catch (logic_error& e) {
caught_logic_error = true;
}
//this is just to prove that the exception happened only when it should
assert(caught_logic_error == destruct_while_unwinding);
//*** Where you'll see the problem
//if T = dont_call_terminate and test_stack_unwind = true, snacks don't get eaten, even though they could be
cout << "ruhroh_t<" << T::name << ',' << boolalpha << destruct_while_unwinding << ">()\t"
<< "~" << T::name << "() threw?: \t" << boolalpha << have_scooby_snacks << endl;
}
void bad_part() {
ruhroh_t<throw_when_destruct,false>();
ruhroh_t<throw_when_destruct,true>();
ruhroh_t<dont_call_terminate,false>();
//herein lies the problem
ruhroh_t<dont_call_terminate, true>();
}
/* output:
$ ./dont_terminate
ruhroh_t<throw_when_descruct,false>() ~throw_when_descruct() threw?: false
ruhroh_t<throw_when_descruct,true>() ~throw_when_descruct() threw?: false
ruhroh_t<dont_call_terminate,false>() ~dont_call_terminate() threw?: false
ruhroh_t<dont_call_terminate,true>() ~dont_call_terminate() threw?: true
*/
int main(int argc, char** argv) {
set_terminate(terminate_called); //so it is painfully obvious when we mess up
good_part();
bad_part();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment