How does an exception specification affect virtual destructor overriding?
Asked Answered
O

1

14

The C++ Standard states the following about virtual functions that have exception specifications:

If a virtual function has an exception-specification, all declarations, including the definition, of any function that overrides that virtual function in any derived class shall only allow exceptions that are allowed by the exception-specification of the base class virtual function (C++03 §15.4/3).

Thus, the following is ill-formed:

struct B {
    virtual void f() throw() { } // allows no exceptions
};
struct D : B {
    virtual void f() { }         // allows all exceptions
};

(1) Does this rule apply to destructors? That is, is the following well-formed?

struct B {
    virtual ~B() throw() { }
};
struct D : B {
    virtual ~D() { }
};

(2) How does this rule apply to an implicitly declared destructor? That is, is the following well-formed?

struct B {
    virtual ~B() throw() { }
};
struct D : B { 
    // ~D() implicitly declared
};

While in the general case one should never write an exception specification, this question has practical implications because the std::exception destructor is virtual and has an empty exception specification.

Since it is good practice not to allow an exception to be thrown from a destructor, let's assume for the sake of simplifying any examples that a destructor either allows all exceptions (that is, it has no exception specification) or it allows no exceptions (that is, it has an empty exception specification).

Opprobrious answered 12/7, 2010 at 23:32 Comment(0)
O
14

(1) Does this rule apply to destructors?

Yes, this rule applies to destructors (there is no exception to the rule for destructors), so this example is ill-formed. In order to make it well-formed, the exception specification of ~D() must be compatible with that of ~B(), e.g.,

struct B {
    virtual ~B() throw() { }
};
struct D : B {
    virtual ~D() throw() { }
};

(2) How does this rule apply to implicitly declared special member function?

The C++ Standard says the following about implicitly declared special member functions:

An implicitly declared special member function shall have an exception-specification.

If f is an implicitly declared default constructor, copy constructor, destructor, or copy 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 shall allow all exceptions if any function it directly invokes allows all exceptions, and f shall allow no exceptions if every function it directly invokes allows no exceptions (C++03 §15.4/13).

What functions are directly invoked by an implicitly declared destructor?

After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls

  • the destructors for X’s direct members,
  • the destructors for X’s direct base classes and,
  • if X is the type of the most derived class, its destructor calls the destructors for X’s virtual base classes

(C++03 §12.4/6; reformatted for easier reading).

So, an implicitly declared destructor has an exception specification that allows any exceptions allowed by any of those destructors. To consider the example from the question:

struct B {
    virtual ~B() throw() { }
};
struct D : B { 
    // ~D() implicitly declared
};

The only destructor called by the implicitly declared ~D() is ~B(). Since ~B() allows no exceptions, ~D() allows no exceptions and it is as if it were declared virtual ~D() throw().

This exception specification is obviously compatible with ~B()'s, so this example is well-formed.


As a practical example of why this matters, consider the following:

struct my_exception : std::exception {
    std::string message_;
};

~string() allows all exceptions, so the implicitly declared ~my_exception() allows all exceptions. The base class destructor, ~exception(), is virtual and allows no exceptions, so the derived class destructor is incompatible with the base class destructor and this is ill-formed.

To make this example well-formed, we can explicitly declare the destructor with an empty exception specification:

struct my_exception : std::exception {
    virtual ~my_exception() throw() { }
    std::string message_;
};

While the rule of thumb is never to write an exception specification, there is at least this one common case where doing so is necessary.

Opprobrious answered 12/7, 2010 at 23:33 Comment(6)
Also, it's a bad idea to ever throw an exception from a destructor.Cathee
@Ben Voigt: Agreed; I said that in the last sentence of the question.Opprobrious
I guess I've gotten in the habit of looking for details like that in the answers, not the questions. That's probably ok as long as the answerers are smarter than the questioners. Which leads to the rather odd corollary: Are you smarter than yourself? And how did you get such a detailed answer written the same minute you posted the question?Cathee
@Ben: I was going to post this question this morning, then ended up researching the answer myself because I was interested (I really wasn't particularly familiar with the details of exception specifications), so I decided to pull a GMan and ask then answer the question myself. I'm fairly certain that, if anything, I'm dumber than myself :-). I'd certainly be interested to see other answers to the question, especially if I've missed some detail or some closely related issue.Opprobrious
I stumbled upon this question while researching "looser throw specifier" error with gcc 4.6. It seems like gcc's implicit destructors have NO throw declaration contrary to your struct D : B example.Unicorn
We are trying out a beta version of the Green Hills MULTI 5.2.4 compiler, and it started flagging the destructors of our classes that derive from std::exception. I did some googling and ended up here. I checked the standard and I think your analysis is correct. Nice question and answer. Thanks.Davison

© 2022 - 2024 — McMap. All rights reserved.