6

Another question cites the C++ standard:

3.8/1 "The lifetime of an object of type T ends when: — if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or — the storage which the object occupies is reused or released."

It would seem that this means accessing members of an object from a destructor is not allowed. However this seems to be wrong, and the truth is something more like what's explained in Kerrek SB's answer:

Member objects come alive before a constructor body runs, and they stay alive until after the destructor finishes. Therefore, you can refer to member objects in the constructor and the destructor.

The object itself doesn't come alive until after its own constructor finishes, and it dies as soon as its destructor starts execution. But that's only as far as the outside world is concerned. Constructors and destructors may still refer to member objects.

I'm wondering if in the destructor I can pass the object's address to an outside class, like:

struct Person;
struct Organizer
{
     static void removeFromGuestList(const Person& person); // This then accesses Person members
}

struct Person
{
     ~Person() {
      // I'm about to die, I won't make it to the party
      Organizer::removeFromGuestList(*this);
}

};

This seems OK to me as I think an object's lifetime lasts until after the destructor finishes, however this part of the above answer has me doubting:

The object itself doesn't come alive until after its own constructor finishes, and it dies as soon as its destructor starts execution. But that's only as far as the outside world is concerned. Constructors and destructors may still refer to member objects.

Zebrafish
  • 11,682
  • 3
  • 43
  • 119
  • This maybe doesn't answer the question, but if you decouple `Person` and `Organizer` classes you wouldn't hit such a problem. – vahancho Apr 08 '21 at 11:27
  • @vahancho Yes, I realized it's probably a bad design when I had Person needing Organizer and vice versa. The person dying is a handleID dying, and Organizer needs to delete a buffer. – Zebrafish Apr 08 '21 at 11:30
  • This post asks this question from the other end: https://stackoverflow.com/questions/26074373/is-there-anything-wrong-with-using-an-object-in-its-own-construction – StoryTeller - Unslander Monica Apr 08 '21 at 11:31
  • The destructor can pass the object's address (`this`) or a reference to the object (e.g. `*this`) to some other function. However, since the destructor has been called, the object's lifetime has ended, which constrains what that other function can do with that reference. For example, the other function CAN access members of bases of the object being destructed (since *their* destructors have not yet been executed). However, that function CANNOT save a pointer or reference to the object that will exist after the destructor completes, since that causes undefined behaviour. – Peter Apr 08 '21 at 11:33
  • @Peter Why do you say members of bases? Only bases? If the class doesn't inherit from anything another function can't access its members? – Zebrafish Apr 08 '21 at 11:38
  • @Zebrafish - I only mentioned ability to access members of bases as an example. Accessing non-static members of the object itself (whether it has bases or not) is also possible, since their lifetime ends after the destructor completes. The distinction is that the object ceases to exist when its destructor commences, but it's members (as distinct objects) still exist while the destructor of the object that contains them is executing. – Peter Apr 08 '21 at 11:44
  • @Peter The issue for me is **how** would one refer to the object's members as distinct objects? – Adrian Mole Apr 08 '21 at 11:50
  • @Adrian - Each member (with a constructor and destructor) is constructed before the containing object, and destructed after. – Peter Apr 08 '21 at 11:57
  • @Peter Are there exceptions? Doesn't even fundamental types have constructors and destructors conceptually? – Ted Lyngmo Apr 09 '21 at 22:18
  • @TedLyngmo - As I understand it, there are not exceptions. – Peter Apr 10 '21 at 00:43
  • @Peter I must admit that I don't know if the suggestion to make fundamental types execeptons in C++20 sprung to life. Before C++20, there are none. Not even an `int`"lives" until properly made alive (by a placement `new`). - which "most people" find silly. :-) – Ted Lyngmo Apr 10 '21 at 00:51
  • 1
    @TedLyngmo - IMHO, language lawyers have had too much influence on recent evolution of the C++ standard. There are too many fine philosophical or theoretical points being addressed with little or no practical use, that cause code that was previously clear and correct to have unspecified or undefined behaviour in some corner case only seen in code written by language lawyers. Laws in society are more complicated than needed because of hair-splitting by the legal fraternity. Language lawyers are doing the same to our programming languages. – Peter Apr 10 '21 at 01:10
  • @TedLyngmo - Language lawyering is important for understanding and improving utility of the language. I get annoyed when language lawyering becomes a sport to find holes in the specification and propose fixes that spawn complexities that simply didn't exist before, effectively break existing code, and then only language-lawyers can write correct code. Or introduce subtle corrections to allow constructs, which only a language lawyer would use, become valid. This thing of `int` not being properly alive without use of placement `new` demonstrates language lawyering gone mad, IMHO. – Peter Apr 10 '21 at 01:40
  • @Peter I understand that I'm absolutely contributing to the problem - especially with my recent rant about the objects "coming alive". I have my own agenda, but no leverage. I'd like to make obviously silly things in the language dealt with. I'm however not a good advocate in that respect, I just try to understand the language as best I can and to keep up. It's not easy. – Ted Lyngmo Apr 10 '21 at 02:02

1 Answers1

3

The C++ Standard does seem to be just a little bit self-contradictory regarding the exact status of class members during execution of the destructor.

However, the following excerpt from this Draft C++ Standard may give some reassurance that your call to the removeFromGuestList function should be safe (bold italics formatting added by me):

15.7 Construction and destruction

1   For an object with a non-trivial constructor, referring to any non-static member or base class of the object before the constructor begins execution results in undefined behavior. For an object with a non-trivial destructor, referring to any non-static member or base class of the object after the destructor finishes execution results in undefined behavior.

What remains unclear (at least, to me) is whether or not referring to those class members via a reference to the object being destroyed is valid once the destructor has started execution. That is, assuming your Person class has a member, ObjectType a, is referring to person.a in your removeFromGuestList function valid?

On the other hand, rather than passing *this as its argument, if you were to pass each required member as a 'distinct object', then you would be safe; so, redefining that function as, say, removeFromGuestList(const ObjectType& a) (with possible additional arguments) would be completely safe.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • I came here wondering about a particular case: `struct Foo { struct Shared { Foo* foo; std::mutex m;}; std::shared_ptr sh; Foo() : sh(std::make_shared(Shared(this)) {} ~Foo() { std::unique_lock l(sh->m); sh->foo = nullptr; } };` If someone else does `auto sh = foo.sh;` and tries to access `foo` with `std::unique_lock l(sh->m); f(*sh->foo);`, is that OK? That is, `~Foo()` can *start* execution while another thread is in `(*sh->foo)`, but nothing *happens* to the `Foo` until the mutex unlocks, so it feels like it should be OK. – Ben Feb 07 '22 at 19:19