Exception specification in derived class destructors

Hi there,

I am reading a book on C++ and I have a problem understanding exception specifiers in derived class destructors. The book defines a class which derives from std::logic_error which is one of the C++ standard exception classes:

1
2
3
4
5
6
7
8
9
class MyError: public std::logic_error
{
public:
	explicit MyError(const std::string &s):std::logic_error(s) { }
	MyError(const std::string &s, const std::string &lhs, const std::string &rhs):std::logic_error(s), left(lhs), right(rhs) { }
	virtual ~MyError() throw() { }
	const std::string left, right;
};



The book says:

"The destructors for the standard exception classes include an empty throw() specifier - they promise that they will not throw any exceptions. When we inherit from one of these classes, then our destructor must also promise not to throw any exceptions."

QUESTIONS:

1) Since it wouldn't be the first time that this book says something wrong I am wondering if this is really true. Can anyone point to a paragraph in the ISO C++ standard which specifies that a destructor of a derived class must specify empty exception list (throw()) if the destructor of its base class specified empty exception list?

2) If this is really true I am wondering WHY that is so. Can anyone give me a rationale for this?

3) Why is destructor of MyError declared as virtual?
I know what virtual normally means for destructors - it is used when we want to delete a derived object allocated on the heap through a base class pointer. However an exception is not a "normal" object - it is neither allocated on the heap nor it is deleted by the program (delete p;).


P.S.
I am aware that it is not a good idea for a destructor to throw an exception in the first place. The reason is that destructors are normally called during stack unwinding when an exception is raised but not yet handled. If a destructor raised an exception in that case then the program would be terminated without the first exception being handled. However a destructor MAY raise an exception and it could theoretically be the first/only exception. And like any other function a destructor may specify exceptions that is raises.


thank you for your answers

best regards
What is the date of the book's publication? It sounds like it is not aware of C++11. Nowadays it is assumed that the destructor never throws even if there is no such indication. For other functions, we use noexcept.

3. Because a catch block can catch it as a base class instead of the most derived class, and it needs to be able to deallocate it properly. Remember: exceptions are caught by reference - catching them by value is a mistake.
Last edited on
The book is written before 2011 so it is not aware of C++11.

Nowadays it is assumed that the destructor never throws even if there is no such indication.


Nevertheless, the destructors MAY throw. I doubt that C++11 forbids a destructor to throw since C++03 allows it. That change would be incompatible.


Because a catch block can catch it as a base class instead of the most derived class, and it needs to be able to deallocate it properly. Remember: exceptions are caught by reference - catching them by value is a mistake.


Are you saying that exception handlers must delete exceptions?
I highly doubt that since exceptions are created automatically so they should be destroyed automatically too. I think exceptions are destroyed automatically when the last handler finishes handling the exception. I even doubt that deleting an exception (with delete) would be legal since an exception is not allocated on the heap although the compiler cannot detect illegality of such delete expression. Besides, since the standard allows catching exception by value in that case deleting exceptions would be impossible.

My point is -> I highly doubt that what you said is the real reason for declaring the destructor virtual.
Destructors should never throw: http://stackoverflow.com/q/130117/1959975

std::exception's destructor is virtual, so it is not possible for a deriving class to not have a virtual destructor. Even if you do not specify the virtual keyword, the destructor would still be virtual.

EDIT: You had good reason to doubt me about why the destructor is virtual: http://stackoverflow.com/a/28354014/1959975
I do not know why it is virtual.
Last edited on
So we still have questions 1, 2 and 3 open/unanswered.
All questions are answered - 1 and 2 = the book is outdated, and 3 = std::exception's destructor is already virtual in the first place. If you have new questions as a result of those answers, feel free to ask them in this topic.
Last edited on
are there other opinions?
closed account (E0p9LyTq)
are there other opinions?

My opinion:

What LB said.
> 1) Can anyone point to a paragraph in the ISO C++ standard which specifies that a destructor of a derived class
> must specify empty exception list (throw()) if the destructor of its base class specified empty exception list?

This is required if and only if the base class has a virtual destructor.

There is no special case for destructors; the rule is the same for overrides of any virtual function.
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, unless the overriding function is defined as deleted. - IS


The special rule for destructors is this:
A declaration of a destructor that does not have an exception-specification has the same exception specification as if had been implicitly declared - IS


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct A { virtual ~A() throw() {} };

struct B : A { ~B() {} }; // fine: implicit noexcept
// A declaration of a destructor that does not have an exception-specification
// has the same exception specification as if had been implicitly declared - IS

struct C : A
{
    ~C() {} ; // *** error *** : looser exception specifier
     // 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,
     // unless the overriding function is defined as deleted. - IS

    struct D { ~D() throw(int) { /* ... */ } /* ... */ };
    D d ;
};

clang version 3.6.0 (tags/RELEASE_360/final 235480)
main.cpp:9:5: error: exception specification of overriding function is more lax than base version
    ~C() {} ; // *** error *** : looser exception specifier
    ^
main.cpp:1:20: note: overridden virtual function is here
struct A { virtual ~A() throw() {} };
                   ^
1 error generated.

g++ (GCC) 5.2.0
main.cpp:9:5: error: looser throw specifier for 'virtual C::~C() throw (int)'
     ~C() {} ; // *** error *** : looser exception specifier
     ^
main.cpp:1:20: error:   overriding 'virtual A::~A() throw ()'
 struct A { virtual ~A() throw() {} };

http://coliru.stacked-crooked.com/a/efa01f9075c77d4a


2) If this is really true I am wondering WHY that is so.

A reference to a derived class object can be substituted at run-time for a reference to a base class object.
(The static type of the object is base class; the dynamic type is derived class).

Virtual functions are dispatched based on the dynamic type of the object; it would lead to general insanity if the invariants guaranteed by the base class (against which the code is written) is violated at run-time.

1
2
3
4
struct base 
{
     virtual int foo() throw(a,b,c) ;
};


Any function that overrides this virtual function
a. must return int (the base class promises to return int)
b. must have a dynamic exception specification that is not looser than throw(a,b,c);
ie. throw(a,b) is fine, throw(a,b,c,d) is not (unless d happens to be a derived class of a, b, or c).
Thank you JLBorges for providing excellent answers for questions 1 and 2. So the book I am reading is correct in this matter after all.

Now I consider questions 1 and 2 answered.


I think that only few clarifications are needed:

A declaration of a destructor that does not have an exception-specification has the same exception specification as if had been implicitly declared


The only question here is what is the exception specification for implicitly declared destructor?
ISO C++11 standard, sec. 15.4 Exception specifications, paragraph 14, p. 408 provides an answer:

An implicitly declared special member function (Clause 12) shall have an exception-specification. If f is 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 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.[ Example:

struct A {
A();
A(const A&) throw();
A(A&&) throw();
~A() throw(X);
};

struct B {
B() throw();
B(const B&) throw();
B(B&&) throw(Y);
~B() throw(Y);
};

struct D : public A, public B {
// Implicit declaration of D::D();
// Implicit declaration of D::D(const D&) throw();
// Implicit declaration of D::D(D&&) throw(Y);
// Implicit declaration of D::~D() throw(X, Y);
};

Furthermore, if A::˜A() or B::˜B() were virtual, D::˜D() would not be as restrictive as that of A::˜A, and the program would be ill-formed since a function that overrides a virtual function from a base class shall have an exception-specification at least as restrictive as that in the base class. —end example ]



Btw. I have noticed another bug in Microsoft's C++ compiler (in VS2013) -> the example in JLBorges's post compiles without error!



The answer for question 2 is rather trivial considering the answer for 1 - only overrides of virtual functions (thus destructors also) must have the same (or stricter) exception specification. I was confused thinking that the rule in the book applies for any destructor, I didn't know that this applies only for virtual functions.



Now only question 3 remains open.
I will reformulate question 3:

Why would one want to declare destructor of an exception class virtual?

I know what virtual normally means for destructors - it is used when we want to delete a derived object allocated on the heap through a base class pointer. However an exception is not a "normal" object - it is neither allocated on the heap nor it is deleted by the program (delete p;). Exceptions are created automatically so they should be destroyed automatically too. I think exceptions are destroyed automatically when the last handler finishes handling the exception.

br
Quark wrote:
I doubt that C++11 forbids a destructor to throw since C++03 allows it. That change would be incompatible.

That is in fact what happened. People who use throwing destructors (the SOCI library) had to change their code (add explicit "noexcept(false)") to compile in C++11.
Last edited on
> Why would one want to declare destructor of an exception class virtual?
> However an exception is not a "normal" object - it is neither allocated on the heap nor it is deleted by the program
> Exceptions are created automatically so they should be destroyed automatically too.

We may need to propagate exceptions across thread boundaries; having multiple threads with no support for cross-thread error propagation is not a viable idea.

The crux of the problem is this: how does one make a copy of an exception object (which we would typically catch as a reference to some base class), with no knowledge of the dynamic type of the object? Hence, the need for library/implementation support for std::exception_ptr http://en.cppreference.com/w/cpp/error/exception_ptr , std::current_exception() http://en.cppreference.com/w/cpp/error/current_exception and their friends.

With std::current_exception(), it is implementation-defined whether a copy of the exception object is made, and if a copy is made, whether it is dynamically allocated. To cover all implementation choices, the base class std::exception requires a virtual destructor.

Here's a trivialised example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <future>
#include <stdexcept>
#include <vector>

int mod( int a, int b )
{
    if( b != 0 ) return a % b ;
    else throw std::invalid_argument( "integer divide by zero!" ) ;
}

std::exception_ptr foo( int a, int b, int& m )
{
    try { m = mod(a,b) ; }
    catch(...) { return std::current_exception() ; }
    return {} ;
}

int main()
{
    std::vector< std::future< std::exception_ptr > > futures ;
    std::vector<int> div = { 12, 45, 78, 0, 17 } ;
    std::vector<int> mod( div.size() ) ;
    for( std::size_t i = 0 ; i < div.size() ; ++i )
        futures.push_back( std::async( std::launch::async, foo, 100, div[i], std::ref(mod[i]) ) ) ;

    for( std::size_t i = 0 ; i < div.size() ; ++i )
    {
        std::cout << "100 % " << div[i] << " == " ;
        auto ep = futures[i].get() ;
        if( ep == nullptr ) std::cout << mod[i] << '\n' ;
        else
        {
            try { std::rethrow_exception(ep) ; }
            catch( std::exception& e ) { std::cout << "error: " << e.what() << '\n' ; }
        }
    }
}

http://coliru.stacked-crooked.com/a/88dcb2f08e932d83

A second, more prosaic reason is that exceptions are polymorphic objects anyway; creating them with dynamic storage duration is allowed; and therefore their destructors should be virtual as in any other polymorphic type.


> I have noticed another bug in Microsoft's C++ compiler (in VS2013)

It does tell us that it is non-conforming:
warning C4290: C++ exception specification ignored except to indicate a function is not __declspec(nothrow)

http://rextester.com/WVM56190

The current situation with VS 2015 is interesting: It emits the same warning:
warning C4290: C++ exception specification ignored except to indicate a function is not __declspec(nothrow)
for line 16. But the error that it emits for line 9 makes it clear that the warning is spurious; it hasn't ignored it at all.
error C2694: 'C::~C(void) throw(int)': overriding virtual function has less restrictive exception specification 
                                       than base class virtual member function 'A::~A(void) throw()'


(You should upgrade to Visual Studio 2015.)
Last edited on
Topic archived. No new replies allowed.