10

Let's suppose I want to call an external function of my object to perform some checks inside the body constructor. Since the lifetime of an object begins when the constructor's body finishes its execution, is it an unsafe design?

struct A;

void check(A const&) { /* */ }

struct A
{
    A() { check(*this); }
};

I mean, I'm calling and external function with a not-yet-alive object. Is it undefined behaviour?

Related questions: if I put that checking function as a member function (static or not), what does the standard says about using non-yet-alive objects outside the constructor but inside the class?

It there any difference in the lifetime concept between the point of view of a class and its users (a sort of in-class versus out-class lifetimes)?

Barry
  • 286,269
  • 29
  • 621
  • 977
ABu
  • 10,423
  • 6
  • 52
  • 103
  • 2
    The [C++ FAQ](https://isocpp.org/wiki/faq/ctors#using-this-in-ctors) has information about that. – Jesse Good May 20 '15 at 12:50
  • It should be fine as long as the function is not a member virtual and is not in the initialization list – KABoissonneault May 20 '15 at 12:52
  • 1
    A const& is a reference and is neither initialized at the beginning of check nor destroyed at the end of the functions. Besides inside the constructor the object is fully constructed. I don't see any problem in this code – Brahim May 20 '15 at 12:57
  • @JesseGood I've read the question, but also, this one: https://isocpp.org/wiki/faq/ctors#init-methods. So, the isocpp FAQ is before C++11-era, and I know the rules about object lifetime have changed in the new standard. Although pretty useful, I want to be safe about the rules. – ABu May 20 '15 at 12:57
  • @Brahim But it's a reference to a non-alive object. – ABu May 20 '15 at 12:58
  • Non-alive to whom? If you're in the body of the constructor, every bit of memory for the object which that constructor function has access to has been allocated and is ready for the constructor to deal with. If the constructor can handle it, it can delegate handling it to another function. A reference is in reality just a `* const`, a pointer that can't be made to point to anything else. – Rob K May 20 '15 at 15:33
  • 2
    @RobK To the standard. I'm sending to an external function a well-defined (objects are initialized) but non-alive object. What are the formal consequences of that? How can I explain that to my sons in the future? :) – ABu May 20 '15 at 15:45

1 Answers1

9

The lifetime of A will not have begun when check() is called because, from [base.life]:

The lifetime of an object of type T begins when:

  • storage with the proper alignment and size for type T is obtained, and
  • if the object has non-vacuous initialization, its initialization is complete.

A has non-vacuous initialization. Its initialization is complete when, from [class.base.init]/13:

In a non-delegating constructor, initialization proceeds in the following order:

  • ...
  • — Finally, the compound-statement of the constructor body is executed.

However, despite A not having its lifetime begun yet, the standard additionally provides, in [class.base.init]/16:

Member functions (including virtual member functions, 10.3) can be called for an object under construction... However, if these operations are performed in a ctor-initializer (or in a function called directly or indirectly from a ctor-initializer) before all the mem-initializers for base classes have completed, the result of the operation is undefined.

With regards to lifetime issues, there's no difference between:

void check(const A& ) { .. }
struct A { 
    A() { check(*this); } 
};

And:

struct A {
    void check() const { .. }
    A() { check(); }
};

The latter is explicitly allowed for (as it's not in a ctor-initializer), so I see no reason to exclude the former on lifetime grounds.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • ... except if A is a base class with virtual members which get called by `check()` - then the results may surprise you. – Richard Hodges May 20 '15 at 13:27
  • 1
    @RichardHodges It's not undefined behavior though. – Barry May 20 '15 at 13:46
  • @Barry Are you sure initialization ends there? http://stackoverflow.com/a/20409911/1794803 – ABu May 20 '15 at 13:47
  • @RichardHodges It is not undefined behaviour as Barry said. Simply, dynamic polymorphism doesn't work until the derived object is fully-constructed (because base objects are constructed before), and the base overload is called instead. – ABu May 20 '15 at 13:50
  • @Peregring-lk et. al. You are right, the behaviour is well defined but will nonetheless be surprising for the unwary. – Richard Hodges May 20 '15 at 13:56
  • @Peregring-lk You are right. I reworded that section. – Barry May 20 '15 at 14:07
  • 1
    So, so. The standard doesn't disallow calling external functions from body constructors (provided subobjects are initialized), but also states the object is not yet alive. What sense does defining lifetime in such a way have then? What sense have to call external functions with objects which don't really exist? It has sense to say the object is not yet alive until construction finishes, but, isn't it better to left lifetime undefined or something like that? – ABu May 20 '15 at 14:36
  • @Peregring-lk The definition of "alive" is crazy. Happy? – curiousguy Jun 10 '18 at 22:24