Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What the c++ standard says about loosing throw specifier in default destructor

Tags:

c++

visual-c++

Three different compilers show three different behaviours compiling this code:

class MyException : public std::exception
{
 public:
  MyException(std::string str) : m_str(str) {}
  virtual const char * what() const throw () {return m_str.c_str(); }
 protected:
  std::string m_str;
};

Sun C++ 5.8 Patch 121017-22 2010/09/29: Warning Function MyException::~MyException() can throw only the exceptions thrown by the function std::exception::~exception() it overrides

g++ 3.4.3: Error looser throw specifier for `virtual MyException::~MyException()'

Visual Studio 2005: It is very happy (neither error or warning)

class exception {
public:
  exception () throw();
  exception (const exception&) throw();
  exception& operator= (const exception&) throw();
  virtual ~exception() throw();
  virtual const char* what() const throw();
}

I know what the problem is and how I can fix it:

class MyException : public std::exception
{
 public:
  MyException(std::string str) : m_str(str) {}
  virtual const char * what() const throw () {return m_str.c_str(); }
  ~MyException() throw() {}  <------------ now it is fine!
 protected:
  std::string m_str;
};

However I am wondering what the standard says in specific situation.

I ran another small test in Visual Studio 2005 and I have found something that really surprise me:

struct Base
{
    virtual int foo() const throw() { return 5; }
};

struct Derived : public Base
{
    int foo() const { return 6; }
};

int main()
{
    Base* b = new Derived;
    std::cout << b->foo() << std::endl; //<-- this line print 6!!!
    delete b;
}

The signature of the two functions are different. How can this work? It seems that visual studio 2005 completely ignore the exception specification.

struct Base
{
    virtual int foo() const throw() { return 5; }
};

struct Derived : public Base
{
    int foo() { return 6; } // I have removed the const keyword 
                            // and the signature has changed
};

int main()
{
    Base* b = new Derived;
    std::cout << b->foo() << std::endl; // <-- this line print 5
    delete b;
}

Is this c++ standard? Is there any magic flag to set?

What about VS2008 and VS2010?

like image 918
Alessandro Teruzzi Avatar asked Mar 09 '12 14:03

Alessandro Teruzzi


People also ask

What happens if destructor throws exception?

Destructors are only called for the completely constructed objects. When the constructor of an object throws an exception, the destructor for that object is not called.

Why can'ti throw an exception in a destructor C++?

The C++ rule is that you must never throw an exception from a destructor that is being called during the "stack unwinding" process of another exception. For example, if someone says throw Foo(), the stack will be unwound so all the stack frames between the throw Foo() and the } catch (Foo e) { will get popped.

How do you handle error in destructor?

9. How to handle error in the destructor? Explanation: It will not throw an exception from the destructor but it will the process by using terminate() function. 10.

Which of the following should not throw an exception?

Today I learned that swap is not allowed to throw an exception in C++.


2 Answers

Your program is ill-formed as per the C++ Standard and hence demonstrates a behavior which cannot be explained within the realms of the C++ standard.

Reference:
C++03 Standard:

15.4 Exception specifications [except.spec]

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-specificationof the base class virtual function.

[Example:

 struct B 
 {
    virtual void f() throw (int, double);
    virtual void g();
 };
 struct D: B 
 {
    void f(); // ill-formed
    void g() throw (int); // OK
 };

The declaration of D::f is ill-formed because it allows all exceptions, whereas B::f allows only int and double. ]

like image 114
Alok Save Avatar answered Oct 16 '22 09:10

Alok Save


It evolved a bit in C++11 [except.spec]:

5/ 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.

So you are never actually allowed to specify a looser exception specification.

However this case is tricky because the destructor is actually synthetized by the compiler itself!

In C++03, I think the Standard was not so careful about those, and you had to write them yourself, in C++11 however we get:

14/ 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.

Where the compiler will generate the exception specification of the destructor so that it matches what can be thrown from the functions it calls (ie, the destructors of the attributes). If those destructors do not throw, then it will generate a noexcept destructor which will satisfy the base class constraint.

Note: VS2005 is one of the least Standard compliant compiler you might find on Earth.

like image 5
Matthieu M. Avatar answered Oct 16 '22 09:10

Matthieu M.