Created
August 8, 2012 23:50
-
-
Save cbsmith/3299796 to your computer and use it in GitHub Desktop.
The problem with std::uncaught_exception illustrated
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// -*- 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