1

I'm trying to create a class with a "mostly-invariant" that allows clients to break the invariant if need be but only if they fix it before leaving the scope in which the nastiness happens.

Here are the two classes involved. It's similar to scope guard. More details, comments, and a small test at ideone.

http://ideone.com/dMCHVU

class HCAccessor;

class HasConditions
{
    // class "mostly-invariant"
    // 7 < payload_ <42
    int payload_;

    bool valid() const
    {
        if (!(7 < payload_) || !(payload_ < 42))
            return false;
        else
            return true;
    }

public:
    HasConditions(const int payload)
        : payload_(payload)
    {
        if (!valid())
        {
            throw std::runtime_error("can't construct");
        }
    }

    friend class HCAccessor;
};

class HCAccessor
{
    HasConditions& hc_;

public:
    HCAccessor(HasConditions& hc)
        : hc_(hc)
    {}

    HCAccessor(HCAccessor& other)
        : hc_(other.hc_)
    {}

    ~HCAccessor()
    {
        if (!hc_.valid())
        {
            throw std::runtime_error("you broke it!");
        }
    }

    void payload(const int newval)
    {
        hc_.payload_ = newval;
    }

    int payload() const
    {
        return hc_.payload_;
    }
};

When the "mostly-invariant" is broken and then fixed the code seems to work. When the "mostly-invariant" remains broken and ~HCAccessor() throws, std::terminate gets called and I don't know why. None of the reasons for an exception resulting in a std::terminate call seem to fit.

http://en.cppreference.com/w/cpp/error/terminate

As far as I can tell only one exception is thrown and then immediately std::terminate is called.

Why is this happening and how can I fix it?

Praxeolitic
  • 22,455
  • 16
  • 75
  • 126
  • Don't [throw exceptions in destructors](http://stackoverflow.com/questions/130117/throwing-exceptions-out-of-a-destructor). – Captain Obvlious Jan 02 '15 at 04:55
  • Besides throwing exceptions in destructors being bad, are you actually attempting to *catch* the exception? And how are you handling temporaries of the `HCAccessor` class? Or are you passing it by value anywhere? – Some programmer dude Jan 02 '15 at 04:59

2 Answers2

3

Destructors in C++11 are noexcept by default. If you really want to do this, you have to declare the destructor of HCAccessor with noexcept(false):

~HCAccessor() noexcept(false) {

or an old-fashioned throw-list:

~HCAccessor() throw(std::runtime_error) {

(Kudos to Pradhan for the noexcept(false) syntax that I had not seen before. It's not something one needs often, I suppose).

However, doing so is almost certainly a Bad Idea™. Flying exceptions cause destructors to be called in stack unwinding, and if you have destructors that throw exceptions, you'll eventually find yourself trying to throw several exceptions at the same time. Which explodes.

If an instance of HCAccessor does not manage any resources, it has nothing to do in cleanup, and the destructor is a nop. I don't see how that is a reason to throw an exception -- just leave it be.

Wintermute
  • 42,983
  • 5
  • 77
  • 80
  • I had been thinking about that and if an unrelated exception is thrown and `HasConditions` is also invalid then you really do have two problems at once, it's not just an artifact of this construct. Is there *any* good way to handle that? – Praxeolitic Jan 02 '15 at 05:52
  • Not really. You'll just have to design your classes in such a way that either invalid objects cannot happen or that the destruction of an invalid object does not cause problems. The latter is not usually difficult to do. @Pradhan: Alas, that is sometimes how it goes. I'll blatantly steal the better syntax for the evil case, if you don't mind. – Wintermute Jan 02 '15 at 05:56
2

12.4.3 of the C++11 standard says

A declaration of a destructor that does not have an exception-specification is implicitly considered to have the same exception-specification as an implicit declaration.

15.4.14 says

If f is an inheriting constructor or an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f’s implicit definition; f allows all exceptions if any function it directly invokes allows all exceptions, and f has the exception-specification noexcept(true) if every function it directly invokes allows no exceptions.

Since the implicit destructor for HCAccessor would be trivial and hence noexcept(true) since it doesn't invoke any function, ~HCAcessor, in the absence of an exception specification, will be declared noexcept as well.

Pradhan
  • 16,391
  • 3
  • 44
  • 59