(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.