Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does an exception specification affect virtual destructor overriding?

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

like image 271
James McNellis Avatar asked Jul 12 '10 23:07

James McNellis


1 Answers

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

like image 154
James McNellis Avatar answered Nov 02 '22 18:11

James McNellis