4

From the related c++ standard section :

Referring to any non-static member or base class of an object in the handler for a function-try-block of a constructor or destructor for that object results in undefined behavior.

eg.

T::~T() 
{
      try {
        this->nonstatic_member; // iff I read the quote correctly
      } catch( ... ) {
      }
}

So why is this undefined behaviour ?

Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
  • 9
    It is not, because this is not a function-try-block. – n. m. could be an AI Aug 02 '14 at 10:44
  • @n.m. Just stumbled upon this, could you provide a correct reading of the quote (preferably involving a destructor) ? – Nikos Athanasiou Aug 02 '14 at 10:46
  • 2
    Uh, that should be something like `T::~T() try { }catch(...)` [Live example](http://coliru.stacked-crooked.com/a/a4d070aebae60ef9) – dyp Aug 02 '14 at 10:50
  • The one-word answer: Don't use function-try-blocks. – Kerrek SB Aug 02 '14 at 11:10
  • @KerrekSB Don't know, there's no abolishment of them in the [usual](http://www.drdobbs.com/introduction-to-function-try-blocks/184401297) references and I've been reading on it all morning (the [Q](http://stackoverflow.com/q/25094250/2567683) you answered is what actually got me here). Maybe a new question on the topic would be useful but probably will be close as a dup [of](http://stackoverflow.com/questions/5612486/when-is-a-function-try-block-useful) – Nikos Athanasiou Aug 02 '14 at 11:18
  • I'm not saying that they should be abolished (though they're perfectly useless), only that you shouldn't use them :-) – Kerrek SB Aug 02 '14 at 11:24
  • @CodyGray if you click on the last word of my previous comment I also mention the same post :) though I'm highly tempted to ask so that someone will expose all the cons (have the q ready already) – Nikos Athanasiou Aug 02 '14 at 11:28
  • Oh hah! So you do. Sorry about that. The visited link color is almost impossible to distinguish from regular text on my screen, and it's worse for links with only a few characters. Anyway, maybe you could ask the question a different way. Instead of "when is it useful?", ask what can go wrong. Even better if you can contextualize it somehow, to avoid asking a list-style question. – Cody Gray - on strike Aug 02 '14 at 11:38

3 Answers3

8

I think the reason why accessing non-static data members in a function-try-block of a destructor is that [except.ctor]/2 and [except.handle]/11 guarantee that all subobjects have already been destroyed when entering the catch-clause of said try-block:

github draft from 2014-07-23, [except.ctor]/2

An object of any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructed subobjects (excluding the variant members of a union-like class), that is, for subobjects for which the principal constructor has completed execution and the destructor has not yet begin execution.

[except.handle]/11

[...] The base classes and non-variant members of an object shall be destroyed before entering the handler of a function-try-block of a destructor for that object.

Therefore, whether we throw the exception in the dtor of the class itself, or in the dtor of a subobject: all subobjects will be destroyed.


Example 1:

#include <iostream>

struct loud
{
    ~loud() { std::cout << "~loud()\n"; }
};

struct T
{
    loud l;
    
    ~T() noexcept(false)
    try
    {
        std::cout << "throwing an int\n";
        throw 42;
    }catch(int)
    {
        std::cout << "caught an int\n";
        throw;
    }
};

int main()
{
    try
    {
        T t;
    }catch(...)
    {
        std::cout << "caught an exception in main\n";
    }
}

Output:

throwing an int
~loud()
caught an int
caught an exception in main

Live example


Example 2:

#include <iostream>

struct loud
{
    loud() { std::cout << "loud()\n"; }
    ~loud() { std::cout << "~loud()\n"; }
};

struct A
{
    A() { std::cout << "A()\n"; }
    ~A() noexcept(false) { std::cout << "~A()\n"; throw 42; }
};

struct T
{
    loud l;
    A a;
    
    ~T() noexcept(false)
    try
    {
    }catch(int)
    {
        std::cout << "caught an int\n";
        throw;
    }
};

int main()
{
    try
    {
        T t;
    }catch(...)
    {
        std::cout << "caught an exception in main\n";
    }
}

Output:

loud()
A()
~A()
~loud()
caught an int
caught an exception in main

Live example

Community
  • 1
  • 1
dyp
  • 38,334
  • 13
  • 112
  • 177
  • The same reasoning probably applies to constructors: The fully-constructed bases and members are destructed before entering a function-try-block's catch-clause. – dyp Aug 02 '14 at 15:05
6

A non-static member can be either yet non-created or already destroyed when the handler is processed.

And your example does not demonstrate a function-try-block. It is a try-block inside the body of the destructor when neither subobject is yet destroyed.

Here an example of a function-try block for a constructor

T::T(int ii, double id)
try : i(f(ii)), d(id) 
{
   // constructor statements
}
catch (...) 
{
   // handles exceptions thrown from the ctor-initializer
   // and from the constructor statements
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • Apart from the base class subobjects; why is it UB to access the non-static data members in a function-try-block of a dtor? Their order of destruction is guaranteed, so couldn't you infer which ones are still alive? – dyp Aug 02 '14 at 10:53
  • @dyp Usually the Standard considers general cases. – Vlad from Moscow Aug 02 '14 at 10:55
2

With

struct S
{
    S() try
    : m()
    {

    }
    catch(...)
    {
        // this->non_static is UB
    }

    ~S()
    try
    {

    }
    catch(...)
    {
        // this->non_static is UB
    }
private:
    Member m;
};

When you are in the catch block you cannot be sure from where the exception come from and so which object is initialized/destroyed.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • *"When you are in the catch block you cannot be sure from where the exception come from"* In the general case, yes, but you could have distinct exception types for you different members; or some members whose dtors are noexcept etc. The order of destruction is guaranteed. Same question as I asked on Vlad's answer. – dyp Aug 02 '14 at 10:59
  • @dyp The [construction tracker](http://stackoverflow.com/a/22260634/2567683) idiom lets you know where the exception came from (at least for constructors) – Nikos Athanasiou Aug 02 '14 at 11:04