3

I am learning C++ using the books listed here. Now I came across the following statement from C++ Primer:

When we allocate a block of memory, we often plan to construct objects in that memory as needed. In this case, we’d like to decouple memory allocation from object construction.

Combining initialization with allocation is usually what we want when we allocate a single object. In that case, we almost certainly know the value the object should have.

(emphasis mine)

The important thing to note here is that C++ primer seems to suggest that construction is the same as initialization and that they are different from allocation which makes sense to me.

Note that I've just quoted selected parts from the complete paragraph to keep the discussion concise and get my point across. You can read the complete para if you want here.


Now, I came across the following statement from class.dtor:

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.

(emphasis mine)

Now does the standard specifies exactly when(at what point) the constructor execution begins?

To give you some more context consider the example:

class A {
    public:
        A(int)
        {
     
        } 
};

class B : public A {
    int j;
    public:
         int f()
        {
          return 4;
        }
//------v-----------------> #2
        B() : A(f()),
//------------^-----------> #3
              j(f())
//------------^-----------> #4
        { //<---------------#5
         
        }
};
int main()
{
    B b; #1
    return 0;
}

My questions are:

  1. At what point does the derived class' constructor B::B() start executing according to the standard? Note that I know that A(f()) is undefined behavior. Does B::B() starts executing at point #1, #2, #3, #4 or #5. In other words, does the standard specifies exactly when(at what point) the constructor execution begins?

  2. Is construction and initialization the same in this given example. I mean I understand that in the member initializer list where we have j(f()), we're initializing data member j but does this initialization also implies that the construction B::B() has begun executing at point #4?

I read in a recent SO post that execution of derived ctor begins at point #4 and so that post also seem to suggest that Initialisation and construction is the same.


I read many posts before asking this question but I wasn't able to come up with an answer that is right according to the C++ standard.

I also read this which seems to suggest that allocation, initialization and construction are all different:

  1. Allocation
    This is the step where memory is allocated for the object.
  2. Initialization
    This is the step where the language related object properties are "set". The vTable and any other "language implementation" related operations are done.
  3. Construction
    Now that an object is allocated and initialized, the constructor is being executed. Whether the default constructor is used or not depends on how the object was created.

As you can see above, the user claims that all of the mentioned terms are different as opposed to what is suggested by C++ primer. So which claim is correct here according to the standard, C++ Primer(which says that construction and Initialisation is same) or the above quoted quoted answer(what says that construction and Initialisation are different).

Kal
  • 475
  • 1
  • 16
  • I believe your question is related to placement `new` and the [C++ rule of five](https://cpppatterns.com/patterns/rule-of-five.html) – Basile Starynkevitch Jun 25 '22 at 12:53
  • 1
    These are all intelligently written questions, however: one question per Stackoverflow question, please. Can you trim down the above to just ***one*** specific question, and then after getting an answer post your next question? – Sam Varshavchik Jun 25 '22 at 12:54
  • Related: [CWG issue 1530](https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1530) and [CWG issue 1517](https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1517). The quoted answer is wrong if the the meanings of the terms as in the standard are assumed. But the answer admits that they are not using the official meaning of the terms. C++ primer is probably just equating construction and initialization, because the differences in meaning will not usually be of practical relevance. – user17732522 Jun 25 '22 at 13:04
  • @SamVarshavchik Sorry if it seems that I am asking many questions in one post. My main aim in asking this question was to know at exactly what point labeled above does the execution of derived ctor begin. All the other parts of the question are to support and clarify the answer to that question. Also I wanted this to be a kind of a canonical question so that future readers can clear this concept as there aren't (m)any SO posts discussing this. – Kal Jun 25 '22 at 13:17

2 Answers2

0

Initialization and construction are somewhat similar, but not the same.

For objects of class types, initialization is done by calling a constructor (there are exceptions, e.g. aggregate initialization doesn't use a constructor; and value-initialization zeros the members in before calling a constructor).

Non-class types don't have constructors, so they are initialized without them.

Your C++ Primer quote uses "initialization" and "construction" to refer to the same thing. It would be more correct to call it "initialization" to not limit yourself to class types, and to include other parts of initialization other than a constructor call.


does the standard specifies exactly when(at what point) the constructor execution begins?

Yes. B b; is default-initialization, which, for classes, calls the default constructor and does nothing else.

So the default-constructor B() is the first thing that's executed here.

As described here, B() calls A() after evaluating its argument f(), then initializes j after evaluating its initializer f(). Finally, it executes its own body, which is empty in your case.

Since the body is executed last, it's a common misunderstanding to think that B() itself is executed after A() and/or after initializing j.


I read in a recent SO post that execution of derived ctor begins at point #4

You should also read my comments to that post, challenging this statement.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
-2

There is no distinction between point 1 and 2 in your example. It's the same point. An object's constructor gets invoked when the object gets instantiated, when it comes into existence. Whether it's point 1 or point 2, that's immaterial.

What you are separating out here is the allocation of the underlying memory for the object (point 1) and when the new object's constructor begins executing (point 2).

But that's a distinction without a difference. These are not two discrete events in C++ that somehow can be carried out as discrete, separate steps. They are indivisible, they are one and the same. You cannot allocate memory for a C++ object but somehow avoid constructing it. Similarly you cannot construct some object without allocating memory for it first. It's the same thing.

Now, you do have other distractions that can happen here, like employing the services of the placement-new operator (and manually invoking the object's destructor at some point later down the road). That seems to suggest that allocation is something that's separate, but its really not. The invocation of the placement new operator is, effectively, implicitly allocating the memory for the new object from the pointer you hand over to the placement new operator, then this is immediately followed by the object's construction. That's the way to think about it. So allocation+construction is still an indivisible step.

Another important point to understand is that the first thing that every object's constructor does is call the constructors of all objects that it's derived from. So, from a practical aspect, the very first thing that actually happens in the shown code is that A's constructor gets called first, to construct it, then, once its construction is finished, then B's "real" construction takes place.

B() : A(f()),

That's exactly what this reads. B's constructor starts executing, and its first order of business is to call A's constructor. B does nothing else, until A's constructor finishes. So, technically, B's constructor starts executing first, then A's constructor. But, B's constructor does nothing until A's constructor handles its business.

The first thing that B's constructor does is call A's constructor. This is specified in the C++ standard.

B() : j(f()), A(f())

If you try to do this your compiler will yell at you.

So, to summarize: when you instantiate an object, any object, the very first thing that happens is its constructor gets called. That's it. End of story. Every possible variation here (placement new, PODs with no constructors, the additional song-and-dance routine with virtual dispatch) is all downstream of this, and can be defined as special, and specific, implementations of constructors.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • You said: *"These are not two discrete events in C++ that somehow can be carried out as discrete, separate steps. They are indivisible, they are one and the same. You cannot allocate memory for a C++ object but somehow avoid constructing it."*. This seems incorrect atleast according to C++ Primer. It says: *"The library allocator class, which is defined in the memory header, lets us separate allocation from construction."* Moreover, C++ Primer claims exactly that we can separate allocation and construction. It has an entire section discussing and explaining this. Section 12.2.2 to be exact. – Jason Jun 25 '22 at 14:00
  • The term "allocation" is overloaded. The ***implementation*** of allocation can be separated out. But, C++ itself, will only call that allocator as part of constructing some object, somewhere. It's not going to call it out of thin air, just because it feels like doing it. And you can call that allocator yourself, too. But that's because you can simply call `operator new` yourself, as well. Same thing. – Sam Varshavchik Jun 25 '22 at 14:02
  • C++ Primer explains that `new` combines allocating memory with constructing objects. That is, we can decouple memory allocation from object construction. It claims some other things as well. I think if you read that section yourself it would be better since here in comment section there is word limit and also you'll most probably find some mistakes there(if any). – Jason Jun 25 '22 at 14:08
  • "Combines" is the logical opposite of "decouples". Both cannot be true at the same time. – Sam Varshavchik Jun 25 '22 at 14:12
  • Note that it doesn't say that `new` decouples memory allocation from object construction. It just says: **1)** `new` combines allocating memory with constructing objects. **2)** We can(not using `new`) decouple memory allocation from object construction. That is, it is *not claiming* that we can do both(combine & decouple) using `new`. – Jason Jun 25 '22 at 14:17
  • That would be decoupling ***the implementation*** of memory allocation. It's a very fine, distinct, difference. Whether or not that specific phrasing is used, that's what it is. You can decouple ***the implementation*** of memory allocation. You cannot decouple the allocation, itself, from construction. – Sam Varshavchik Jun 25 '22 at 14:19