gcc exception specification of default destructor

Yordan Pavlov picture Yordan Pavlov · Jun 30, 2011 · Viewed 8k times · Source
class A
{
    public:
    virtual ~A()
    {
    }
};

class B : virtual public A
{
    public:
    ~B() throw()
    {}
};

class C : public B
{
};

int main(int argc, char * argv [])
{
return 0;
}

That code gives the following error:

error: looser throw specifier for ‘virtual C::~C()’
error:   overriding ‘virtual B::~B() throw ()’

on my debian testing ( gcc (Debian 4.6.0-10) 4.6.1 20110526 (prerelease) ) but compiles without errors on previous gcc versions ( 4.5 on my debian system again).

How does an exception specification affect virtual destructor overriding? According to that answer the compiler is supposed to create a default constructor matching the throw declaration of the base class. Obviously this is not what happens on new gcc. What has changed, what is the correct compiler behavior and is there some easy solution to the problem other than manually adding empty destructors in derived classes ( compiler flag for example).

Answer

Alan Stokes picture Alan Stokes · Jul 19, 2011

I presume that in the real code either ~A() or ~B() is declared virtual? (The error message is complaining about a virtual destructor, but in the code as written none of the destructors are virtual.)

I believe the virtual inheritance is what is triggering your problem. C's (implicitly defined) destructor is required to first call ~B() and then, because C is the most-derived class, to call ~A(). (12.4/6)

The generated exception specification for ~C() is required to allow any exception to propagate, because it directly calls ~A() which has no exception specification. (15.4/13)

And that then triggers your error - you can't override a virtual function with a throw() specification (B's destructor) with a version that may throw. (15.4/3)

The solution would be to put throw() on A's destructor. (If you can't do that, then why are you doing it on B's?)

The error also wouldn't happen without the virtual inheritance - because then C's destructor would only call B's destructor. (B's destructor would still call A's - and you're still skating on thin ice, because if A's destructor throws you're going straight to terminate().)