Reputation: 8288
This question about the formalism of the C++ standard semantic. The question is not about implementations or the memory representation of classes.
It's a question about the meaning of a pointer to a subobject of a class object and what makes a pointer to an object a pointer to a subobject. So in a way it's about the nature of pointers.
But pointers are devices to designate subobjects, so more deeply the question is about how an object becomes a subobject, or if it starts existing as a subobject.
During construction, the lifetime of an object has not started. The question is about these pointers available before the lifetime has started.
In C++ you can manipulate a pointer to an object currently being constructed, even save it to use it when the lifetime of the object has started (when it's fully constructed).
The this
pointer is available as soon as you begin doing construction or initialization of subobjects in the ctor-init-list and later in constructor body. (In C and C++, pointers have been available extremely early, even before the object initialization started.)
It means that these "early" pointers to object can't point to a normal constructed object, or even an object that has starting construction its own members. So what do they point to?
Let's say we create a complete object of class type C
and one of its subobject of class type S
saves this
, say in a data member m
, during construction.
struct S {
S *m;
S() : m(this) {}
};
(An alternative would be to save this
to a static variable in the constructor body; that shouldn't really matter.)
S
can be:
After construction of C
, does m
point to the subobject of the C
object?
In the base class type, does m
point to the derived object as a pointer converted to the base class? In other words: what a pointer to a base class subject really is?
When is an object constructed as part of another object really a subobject? When the constructor of the super object calls the constructor of the subobject? Or when construction of the super object completes?
Can you have a subobject of a yet not constructed super object?
EXAMPLE CODE
For example:
struct C1 {
S m_c1;
};
struct C2 : S {
};
C1 c1;
C2 c2;
I suppose that c1.m_c1.m
points to c1.m_c1
, a subobject. Does c2.m
point to the base class subobject S
of C2
or to C2
itself?
Upvotes: 1
Views: 794
Reputation: 72431
A pointer can point to an object, and a reference can refer to an object, even before the object's lifetime begins, and even before its initialization starts.
Using this
is a relatively tame version of this sort of thing, since the initialization of the object *this
has at least started when evaluating the constructor's first mem-initializer.
Restrictions on use of a pointer or reference to an object, or a name of that object, before that object's initialization starts are in [basic.life]/6-7 and [class.cdtor]/1,3. Essentially, it can only be used as memory, to form other pointers and references to the same type; you can't even name its subobjects yet.
Restrictions on use of a pointer or reference to or name for a class-type object during execution of its constructor (including evaluation of a mem-initializer) are in [class.cdtor]/2,4-6. One particularly interesting point connected to your questions is the rule in paragraph 2, which says that pointers and references ultimately derived from a this
keyword for that object can be used to access the values of initialized subobjects, but if other names, pointers, or references are used in the same way, the behavior is unspecified.
Let's say we create a complete object of class type
C
and one of its subobject of class typeS
saves this, say in a data memberm
, during construction. ... After construction ofC
, doesm
point to the subobject of theC
object?
Yes, because as soon as the constructor of the subobject of type S
begins, the value of this
in its scope is a pointer to that subobject. The constructor of the S
subobject is executed during execution of the constructor of the C
object, meaning that the C
already "has" its subobjects at this point, at least via the this
pointer value within the C
constructor definition. For example, we could have:
class S {
public:
S();
int number;
};
class C {
public:
C();
static S* current_s;
private:
class SetCurrS {
explicit SetCurrS(S* s_ptr) { current_s = s_ptr; }
};
SetCurrS before_s;
S s;
};
C global_c;
C::C() : before_s(&this->s), s() { current_s = nullptr; }
S::S() : number(0) {
// Will compare equal (assuming single thread):
if (this == current_s) std::cout << "equals saved pointer\n";
// Also will compare equal:
if (this == &global_c.s) std::cout << "equals global subobject\n";
// Definitely zero:
std::cout << "my number: " << number << '\n';
// UNSPECIFIED BEHAVIOR:
std::cout << "global's number: " << global_c.s.number << '\n';
}
In the base class type, does m point to the derived object as a pointer converted to the base class? In other words: what a pointer to a base class subject really is?
m
points at the base class subobject, period. In terms of the C++ Standard virtual machine, the fact that this is a subobject of some other object is more a property of that subobject, not really an important part of a pointer value which points at the subobject. See [basic.compound]/3:
Every value of pointer type is one of the following:
a pointer to an object or function (the pointer is said to point to the object or function), or
a pointer past the end of an object ([expr.add]), or
the null pointer value ([conv.ptr]) for that type, or
an invalid pointer value.
When a pointer value which points at a derived object is converted to a pointer to base class type, the result is a pointer value which points at the base class subobject. And this pointer value is the same as any other pointer value pointing at that subobject, including the value of this
when validly used in a constructor or other non-static member function called for that subobject.
Upvotes: 0
Reputation: 14673
Actually subobject is three separate things in C++ standard sharing one property - being part of another object:
Objects can contain other objects, called subobjects. A subobject can be a member subobject, a base class subobject, or an array element. An object that is not a subobject of any other object is called a complete object.
When is an object constructed as part of another object really a subobject?
In your example, the only subobject of class S
got type S*
. Whatever it points at isn't a subobject, because it's not part of "super object" storage. An array is also an object, so its elements are subobjects of containing class.
If an object is created in storage associated with a member subobject or array element e (which may or may not be within its lifetime), the created object is a subobject of e's containing object if:
(2.1) the lifetime of e's containing object has begun and not ended, and
(2.2) the storage for the new object exactly overlays the storage location associated with e, and
(2.3) the new object is of the same type as e (ignoring cv-qualification).
Static members of a class are not associated with the objects of the class: they are independent variables with static or thread duration, their relation to object within which they are declared got purpose of code organization.
As namespaces or blocks aren't objects, any object constructed by a declaration within namespace(inlcuding global scope), function or closure, is a complete object. Any object constructed with storage created via new expression is a complete object. placement new reuses storage, so it makes possible to create a subobject within existing and constructed object.
Can you have a subobject of a yet not constructed super object?
No you can't, but base class subobject would be initialized before any other subobjects of complete object, which doesn't mean that base subobject is constructed first, it means that complete objects's construction is not finished.
There is example similar to your initialization case [class.base.init]:
Names in the expression-list or braced-init-list of a mem-initializer are evaluated in the scope of the constructor for which the mem-initializer is specified.
class X {
int a;
int b;
int i;
int j;
public:
const int& r;
X(int i): r(a), b(i), i(i), j(this->i) { }
};
[ Note: Because the mem-initializer are evaluated in the scope of the constructor, the this pointer can be used in the expression-list of a mem-initializer to refer to the object being initialized. ]
Upvotes: -1
Reputation: 474046
When is an object constructed as part of another object really a subobject?
Whether an object is a subobject of another object is a core property of the object, one determined by the nature of its creation. It is not a property that can be dynamically acquired or removed. Objects do not "become" subobjects. Every object is either complete or a subobject, always, and there is no mechanism to change such a status.
Objects can contain other objects, called subobjects. A subobject can be a member subobject ([class.mem]), a base class subobject (Clause [class.derived]), or an array element. An object that is not a subobject of any other object is called a complete object.
There are specific rules for creating subobjects dynamically (like with placement-new
), but even then, such objects are subobjects at the moment of their creation:
If an object is created in storage associated with a member subobject or array element e (which may or may not be within its lifetime), the created object is a subobject of e's containing object if: <some rules>
Can you have a subobject of a yet not constructed super object?
Yes. It happens in every constructor of any object with subobjects. The containing object's constructor has not yet completed, but once the body of the constructor starts, all subobjects have been initialized and are therefore within their lifetimes.
The section on how members and bases get initialized constantly refers to them as "subobjects". And since this process happens during the object's construction, I think it's safe to say that this happens.
It means that these "early" pointers to object can't point to a normal constructed object, or even an object that has starting construction its own members. So what do they point to?
You can think of the status of an object's construction and such as being a dynamic property of the object (unlike whether it is a subobject or not). That is, an object either has not had its lifetime start, is within its lifetime, or is after its lifetime has ended. The object can transition between these states due to various events, but it's still the same object.
That is, if you have an object which is being constructed, it's still the same object after it its lifetime has started. Pointers don't care about the lifetime state of the object they point to; they still point to it.
I can't really quote something from the standard, because there's nothing in the standard which suggests that an object whose lifetime has not started is a different object when its lifetime actually starts. Therefore, since pointers point to specific objects, there's no reason to expect that the change in lifetime status of an object by itself affects what object it points to.
Upvotes: 3