1

This question is inspired by Using an object after it's destructor is called

Let's consider the following code

class B 
  {
  public:
    B() { cout << "Constructor B() " << endl; }
    ~B() { cout << "Destructor ~B() " << endl; }
  };

class A {
public:
  B ob;
  A()
      try
      { 
      throw 4;
      }
    catch(...)
      {
      cout << "Catch in A()\n";
      }

  A(int)
    {
    try
      {
      throw 4;
      }
    catch(...)
      {
      cout << "Catch in A(int)\n";
      }
    }
  };

int main()
  {
  try
  {
      A f;
  }
  catch (...)
  {
      cout << "Catch in main()\n\n";
  }
  A g(1);
  }

It's output is

Constructor B() 
Destructor ~B() 
Catch in A()
Catch in main()

Constructor B() 
Catch in A(int)
Destructor ~B() 

In contrast to A(int), constructor A() has the initializer list try/catch syntax. Why that makes a difference on the order of the subobject destruction? Why the exception thrown in A() propagates to main()?

Community
  • 1
  • 1
Andriy
  • 8,486
  • 3
  • 27
  • 51

3 Answers3

3

Why that makes a difference on the order of the subobject destruction?

When catch in A(int) - all subobjects are alive, and you can use them. Moreover, after catch, you can continue object construction, and "return" to user properly constructed object.

When catch in A() - all subobjects, which were constructed - are destructed. And you don't know which subobjects were constructed and which not - at least with current ISO C++ syntax.

Why the exception thrown in A() propagates to main()?

Check GotW #66:

If the handler body contained the statement "throw;" then the catch block would obviously rethrow whatever exception A::A() or B::B() had emitted. What's less obvious, but clearly stated in the standard, is that if the catch block does not throw (either rethrow the original exception, or throw something new), and control reaches the end of the catch block of a constructor or destructor, then the original exception is automatically rethrown.

Think about what this means: A constructor or destructor function-try-block's handler code MUST finish by emitting some exception. There's no other way. The language doesn't care what exception it is that gets emitted -- it can be the original one, or some other translated exception -- but an exception there must be! It is impossible to keep any exceptions thrown by base or member subobject constructors from causing some exception to leak beyond their containing constructors.

In fewer words, it means that:

If construction of any base or member subobject fails, the whole object's construction must fail.

Community
  • 1
  • 1
Evgeny Panasyuk
  • 9,076
  • 1
  • 33
  • 54
2

The difference is that at the end of:

A()
try
{ 
  throw 4;
}
catch(...)
{
  cout << "Catch in A()\n";
}

the exception is implicitly rethrown and no object A get constructed, whereas in:

A(int) {
try
{ 
  throw 4;
}
catch(...)
{
  cout << "Catch in A(int)\n";
}
}

you swallow the exception and an instance of A is fully constructed.

Destructors run only on fully constructed objects, that is objects whose constructor finished successfully, without throwing an exception.

EDIT: as per the destruction of subobjcets, the catch in the first case is run after the sub-object has been destructed. This is consistent with the member initialization syntax that suggests that it is what should actually happen:

A()
try : ob() // default construct
{ 
  throw 4;
}
catch(...)
{
  // here ob is already destructed
  cout << "Catch in A()\n";
}

(equivalent to the first case.)

Yakov Galka
  • 70,775
  • 16
  • 139
  • 220
1

Why that makes a difference on the order of the subobject destruction?

In general, in the function catch clause of A(), you wouldn't know which members had been successfully constructed, because the exception might have come from one of their constructors. So to remove doubt they're destroyed first. Basically the function try/catch is "outside" the data member construction.

Why the exception thrown in A() propagates to main()?

The function catch clause can't make the constructor succeed (because if its members weren't constructed successfully, then the object itself hasn't been constructed successfully). So if you don't throw something else from it then the original exception is rethrown. That's just how it's defined, you can't use a function-try clause to ignore the problem. You can use a regular try/catch inside a function to ignore a problem, then it's up to you to decide whether or not the problem has prevented the object from being correctly constructed.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699