c++exceptiondestructorstack-unwinding

What happens if an exception leaves a destructor *not* during stack unwinding?


I'm experimenting with writing a Design-by-Contract library. One of my types is defined as:

#include <stdexcept>
#include <type_traits>
#include <utility>

template <typename T, typename Post>
class out {
  public:
    out(T& param, Post check = {})
      : param{param},
        copy{param}, //must be copyable; may throw
        check{std::move(check)}
    {}

    ~out() noexcept(false) {
        if (!check(param)) {
            param = std::move(copy); //rollback; assume this never throws
            if (!std::uncaught_exceptions()) //no exception currently being thrown
                throw std::invalid_argument("postcondition violated"); // leaves destructor if another exception is not being thrown
        }
    }

  private:
    T& param;
    T copy;
    Post check;
};

Here's an example of how it would be used:

#include <vector>
#include <cassert>

struct is_not_empty {
    bool operator()(const std::vector<int>& v) { return !v.empty(); }

    /*not real code, just meant to illustrate the problem*/
    is_not_empty() { /*acquire some resource*/ }
    ~is_not_empty() { /*release some resource*/ }
    /******/
};

void function_with_postcondition(out<std::vector<int>, is_not_empty> v) {
} // Exception thrown here in out<>::~out

int main() {
    std::vector<int> v; //empty
    try {
        function_with_postcondition(v);
        assert(false); //not reached
    }
    catch (const std::invalid_argument& e) {
    }
}

My question(s) - is the v object properly destructed in function_with_postcondition()? Does the out<>::Post member get properly destructed? Do I have a resource leak in this example? Am I recursively calling a destructor? Is this well-defined? What I'm really trying to get at is the (well-defined/undefined) order of operations of what exactly happens.

I know about stack unwinding, and I know throwing an exception while an exception is already being thrown results in a call to std::terminate() - that's what I'm trying to avoid with the call to std::uncaught_exceptions() in the destructor. If the function throws an exception, I don't want to check the postcondition because the function exited abnormally.

Finally, why not do this another way (e.g. assert(), abort(), exit(), etc.)? I'm attempting to write a library that checks function pre- and post-conditions that is robust in anomalous environments. I'm trying to reduce the amount of boilerplate needed, while allowing for commit/rollback semantics that allow operations to be retried.


Solution

  • The quote [except.ctor]

    3 If the destructor of an object is terminated by an exception, each destructor invocation that would be performed after executing the body of the destructor ([class.dtor]) and that has not yet begun execution is performed.

    So we are guaranteed there is at least an attempt to call destructors of members and bases before unwinding the stack. This is all well and good so long as the other objects in question are reasonably well-behaved. If you compose your template with other types that let destructors throw, expect a swift call to std::terminate.

    Also, be careful when plugging your types into standard library templates. Most (almost all) will have undefined behavior if a destructor from a supplied user type ever throws.