Calling methods of unconstructed objects: Legal? - c++

If memory is set aside for an object (e.g., through a union) but the constructor has not yet been called, is it legal to call one of the object's non-static methods, assuming the method does not depend on the value of any member variables?
I researched a bit and found some information about "variant members" but I couldn't find info pertaining to this example.
class D {
public:
D() { printf("D constructor!\n"); }
int a = 123;
void print () const {
printf("Pointer: %p\n", &a);
};
};
class C {
public:
C() {};
union {
D memory;
};
};
int main() {
C c;
c.memory.print();
}
In this example, I'm calling print() without the constructor ever being called. The intent is to later call the constructor, but even before the constructor is called, we know where variable a will reside. Obviously the value of a is uninitialized at this point, but print() doesn't care about the value.
This seems to work as expected when compiling with gcc and clang for c++11. But I'm wondering if I'm invoking some illegal or undefined behavior here.

I believe this is undefined behavior. Your variant member C::memory has not been initialized because the constructor of C does not provide an initializer [class.base.init]/9.2. Therefore, the lifetime of c.memory has not begun at the point where you call the method print() [basic.life]/1. Based on [basic.life]/7.2:
Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways. […] The program has undefined behavior if:
[…]
the glvalue is used to call a non-static member function of the object, or
[…]
emphasis mine
Note: I am referring to the current C++ standard draft above, however, the relevant wording is basically the same for C++11 except that, in C++11, the fact that D has non-trivial initialization is crucial as what you're doing may otherwise potentially be OK in C++11…

Related

Reusing data member storage via placement new during enclosing object's lifetime

This is a follow-up to my previous question where I seem to have made the problem more involved than I had originally intended. (See discussions in question and answer comments there.)
This question is a slight modification of the original question removing the issue of special rules during construction/destruction of the enclosing object.
Is it allowed to reuse storage of a non-static data member during the lifetime of its enclosing object and if so under what conditions?
Consider the program
#include<new>
#include<type_traits>
using T = /*some type*/;
using U = /*some type*/;
static_assert(std::is_object_v<T>);
static_assert(std::is_object_v<U>);
static_assert(sizeof(U) <= sizeof(T));
static_assert(alignof(U) <= alignof(T));
struct A {
T t /*initializer*/;
U* u;
void construct() {
t.~T();
u = ::new(static_cast<void*>(&t)) U /*initializer*/;
}
void destruct() {
u->~U();
::new(static_cast<void*>(&t)) T /*initializer*/;
}
A() = default;
A(const A&) = delete;
A(A&&) = delete;
A& operator=(const A&) = delete;
A& operator=(A&&) = delete;
};
int main() {
auto a = new A;
a->construct();
*(a->u) = /*some assignment*/;
a->destruct(); /*optional*/
delete a; /*optional*/
A b; /*alternative*/
b.construct(); /*alternative*/
*(b.u) = /*some assignment*/; /*alternative*/
b.destruct(); /*alternative*/
}
Aside from the static_asserts assume that the initializers, destructors and assignments of T and U do not throw.
What conditions do object types T and U need to satisfy additionally, so that the program has defined behavior, if any?
Does it depend on the destructor of A actually being called (e.g. on whether the /*optional*/ or /*alternative*/ lines are present)?.
Does it depend on the storage duration of A, e.g. whether /*alternative*/ lines in main are used instead?
Note that the program does not use the t member after the placement-new, except in the destructor and the destruct function. Of course using it while its storage is occupied by a different type is not allowed.
Also note that the program constructs an object of the original type in t before its destructor is called in all execution paths since I disallowed T and U to throw exceptions.
Please also note that I do not encourage anyone to write code like that. My intention is to understand details of the language better. In particular I did not find anything forbidding such placement-news as long as the destructor is not called, at least.
If a is destroyed (whether by delete or by falling out of scope), then t.~T() is called, which is UB if t isn't actually a T (by not calling destruct).
This doesn't apply if
the destructor of T is trivial, or
for delete U is derived from T, or
you're using a destroying delete
After destruct is called you are not allowed to use t if T has const or reference members (until C++20).
Apart from that there is no restriction on what you do with the class as written as far as I can see.
This answer is based on the draft available at http://eel.is/c++draft/
We can try to apply (by checking each condition) what I've decided to call the "undead object" clause to any previous object that used to exist, here we apply it to the member t of type T:
Lifetime [basic.life]/8
If, after the lifetime of an object has ended and before the storage
which the object occupied is reused or released, a new object is
created at the storage location which the original object occupied, a
pointer that pointed to the original object, a reference that referred
to the original object, or the name of the original object will
automatically refer to the new object and, once the lifetime of the
new object has started, can be used to manipulate the new object, if:
(8.1) the storage for the new object exactly overlays the storage
location which the original object occupied, and
(8.2) the new object is of the same type as the original object
(ignoring the top-level cv-qualifiers), and
(8.3) the original object is neither a complete object that is
const-qualified nor a subobject of such an object, and
(8.4) neither the original object nor the new object is a
potentially-overlapping subobject ([intro.object]).
Conditions 1 and 2 are automatically guaranteed by the use of placement new on the old member:
struct A {
T t /*initializer*/; (...)
void destruct() { (...)
::new(static_cast<void*>(&t)) T /*initializer*/;
}
The location is the same and the type is the same. Both conditions are easily verified.
Neither A objects created:
auto a = new A;
...
A b; /*alternative*/
are const qualified complete objects so t isn't a member of a const qualified complete object. Condition 3 is met.
Now the definition of potentially-overlapping is in Object model [intro.object]/7:
A potentially-overlapping subobject is either:
(7.1) a base class subobject, or
(7.2) a non-static data member declared with the no_­unique_­address
attribute.
The t member is neither and condition 4 is met.
All 4 conditions are met so the member name t can be used to name the new object.
[Note that at no point I even mentioned the fact the subobject isn't a const member not its subobjects. That isn't part of the latest draft.
It means that a const sub object can legally have its value changed, and a reference member can have its referent changed for an existing object. This is more than unsettling and probably not supported by many compilers. End note.]

Can I forward construction in the body of a constructor?

Let's consider that during the execution of the constructor of a class S, it appears that S could be constructed using another constructor. One solution could be to make a placement new at this to reuse the storage:
struct S{
unsigned int j; //no const neither reference non static members
S(unsigned int i){/*...*/}
S(int i){
if (i>=0) {
new (this) S(static_cast<unsigned int>(i));
return;}
/*...*/
}
};
int i=10;
S x{i};//is it UB?
Storage reuse is defined in [basic.life]. I don't know how to read this section when the storage is (re)used during constructor execution.
The standard is completely underspecified in this case, and I cannot find a relevant CWG issue.
In itself, your placement new is not UB. After all, you have storage without an object, so you can directly construct an object in it. As you correctly said, the lifetime of the first object hasn't started yet.
But now the problem is: What happens to the original object? Because normally, a constructor is only called on storage without an object and the end of constructor marks the start of the lifetime of the object. But now there is already another object. Is the new object destroyed? Does it have no effect?
The standard is missing a paragraph in [class.cdtor] that says what should happen if a new object is created in the storage of an object under construction and destruction.
You can even construct even weirder code:
struct X {
X *object;
int var;
X() : object(new (this) X(4)), var(5) {} // ?!?
X(int x) : var(x) {}
} x;
is it UB?
No it is not. [basic.life]/5 says:
A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.
Emphasis on the part relevant to your class which has a trivial destructor. About the specific new (this) T; form, I found no exception to this rule in [class.cdtor] nor [class.dtor].

Is it safe to "initialize" an object of type T with *this = T() in a constructor of T?

Is the constructor Year() safe in this case ?
struct Year {
int year;
Year(int y) : year(y) {}
Year() { *this = Year(1970); } // *this = this->operator=(Year(1970));
};
Year y;
I think yes, because year has already been initialized with int() once the execution flows reaches the constructor body. Are there other problems to consider?
Don't consider other cases in which the same trick might cause troubles.
Sure, this will work, and is valid.
Explanation
All data members and bases have already been constructed by the time your ctor-body runs, and:
[n3290: 12.7/4] Member functions, including virtual functions
(10.3), can be called during construction or destruction (12.6.2). [..]
Not to be confused with:
[n3290: 12.7/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.
(NB. "before the constructor begins"; this clause does not apply here.)
And there's nothing in 12.8 "Copying and moving class objects" to prohibit assignment during construction.
Caveat
Note that this does not mean that the object has begun its "lifetime":
[n3290: 3.8/1]: 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-trivial initialization, its initialization is complete.
And the final step in "initialization" for a non-delegating ctor:
[n3290: 12.6.2/10]: [..] Finally, the compound-statement of the constructor body is executed.
Put altogether, this means that an object's "lifetime" doesn't begin until its most derived constructor body has finished executing.
In particular, passing a pointer to the object before it has started its life is not very useful, as doing almost anything through that pointer invokes Undefined Behaviour:
[n3290: 3.5/8]: Before the lifetime of an object has started but
after the storage which the object will occupy has been allocated
or, after the lifetime of an object has ended and before the storage
which the object occupied is reused or released, any pointer that
refers to the storage location where the object will be or was located
may be used but only in limited ways. [..]
However:
[n3290: 3.8/3]: [..] [ Note: [..] Also, the behavior of an object
under construction and destruction might not be the same as the
behavior of an object whose lifetime has started and not ended. 12.6.2
and 12.7 describe the behavior of objects during the construction and
destruction phases. —end note ]
And, as we've already explored, 12.7 kindly informs us that members may be accessed during this phase of construction.
Suggestion
Your approach is hard to follow, though; I also had to look up the above passage before I could be sure that it's valid, so it's clearly not entirely intuitive.
Fortunately C++0x introduces constructor delegation, so that you can write:
struct Year {
Year() : Year(1970) {}
Year(int y) : year(y) {}
int year;
};
(Alas, GCC 4.5.1 doesn't support this, so I cannot demonstrate it to you on ideone.com. In fact, for GCC as a whole there's only a "partial patch" at time of writing.)
What you're doing works, but what wouldn't work would be if you somehow recursively called a constructor ... that would cause problems. But since the constructor is called after all non-static data-members of the object have been constructed, and the this pointer is pointing to valid memory that can be copied into, what you've done is okay--it's not standard practice, but it's okay.
This answer is probably wrong; see Tomalak's answer and the comments. I'll leave it for historic reasons, but you're probably OK calling member functions during the constructor.
Most definitely not. If you have any T*, say p, then you may only invoke member functions via p-> if p points to an object. But an object only begins its life when its constructor has finished. So while you're in the middle of the constructor, this does not point to an object! Therefore, you can most certainly not invoke any member functions like the assignment operator on this not-yet object.
[Curiosum: It is legit to say delete this; in certain situations, you just have to make sure that the destructor does not refer to this in any way. It is also OK to pass this to some other place during the constructor, as long as it is not dereferenced, much like you can pass any garbage pointer around as long as you don't use it.]

Lifetime of temporary bound to aggregate initialized struct member

Given the following code:
class foo
{
};
class bar: public foo
{
public:
~bar() { printf("~bar()\n"); }
};
class zab: public foo
{
public:
~zab() { printf("~zab()\n"); }
};
struct foo_holder
{
const foo &f;
};
int main()
{
foo_holder holder[]= { {bar()}, {zab()} };
printf("done!\n");
return 0;
}
the output is:
~bar()
~zab()
done!
C++0x has a clause that dictates this can create dangling references when used as a new initializer, but it says nothing (at least nothing I can find) about aggregate initialization of const references with temporaries.
Is this unspecified behavior then?
It isn't mentioned in the list of exceptions, therefore the lifetime to temporary should be extended to match lifetime of (array of) foo_holders. However, this looks like oversight to me, perhaps submitting Defect Report might be good idea.
§12.2/5 states, that when reference is bound to a temporary, the lifetime of temporary is extended to match lifetime of the reference and because const foo& f is member of foo_holder, the lifetime of the reference is matching lifetime of foo_holder, according to §3.7.5/1:
The storage duration of member subobjects, base class subobjects and array elements is that of their complete object (1.8).
This might be little bit tricky to interpret considering references, because §3.8/1 states, that lifetime of object ends when the storage is released or reused:
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.
however, it is left unspecified whether references use storage or not; §8.3.2/4 says
It is unspecified whether or not a reference requires storage (3.7).
Perhaps someone with better knowledge of standard would know this better.
I got an answer on comp.std.c++:
http://groups.google.com/group/comp.std.c++/msg/9e779c0154d2f21b
Basically, the standard does not explicitly address it; therefore, it should behave the same as a reference declared locally.

If changing a const object is undefined behavior then how do constructors and destructors operate with write access?

C++ standard says that modifying an object originally declared const is undefined behavior. But then how do constructors and destructors operate?
class Class {
public:
Class() { Change(); }
~Class() { Change(); }
void Change() { data = 0; }
private:
int data;
};
//later:
const Class object;
//object.Change(); - won't compile
const_cast<Class&>( object ).Change();// compiles, but it's undefined behavior
I mean here the constructor and destructor do exactly the same thing as the calling code, but they are allowed to change the object and the caller is not allowed - he runs into undefined behavior.
How is it supposed to work under an implementation and according to the standard?
The standard explicitly allows constructors and destructors to deal with const objects. from 12.1/4 "Constructors":
A constructor can be invoked for a const, volatile or const volatile object. ... const and volatile semantics (7.1.5.1) are not applied on an object under construction. Such semantics only come into effect once the constructor for the most derived object (1.8) ends.
And 12.4/2 "Destructors":
A destructor can be invoked for a const, volatile or const volatile object. ... const and volatile semantics (7.1.5.1) are not applied on an object under destruction. Such semantics stop being into effect once the destructor for the most derived object (1.8) starts.
As background, Stroustrup says in "Design and Evolution of C++" (13.3.2 Refinement of the Defintion of const):
To ensure that some, but not all, const objects could be placed read-only memory (ROM), I adopted the rule that any object that has a constructor (that is, required runtime initialization) can't be place in ROM, but other const objects can.
...
An object declared const is considered immutable from the completion of the constructor until the start of its destructor. The result of a write to the object between those points is deemed undefined.
When originally designing const, I remember arguing that the ideal const would be an object that is writable until the constructor had run, then becomes read-only by some hardware magic, and finally upon the entry into the destructor becomes writable again. One could imagine a tagged architecture that actually worked this way. Such an implementation would cause a run-time error if someone could write to an object defined const. On the other hand, someone could write to an object not defined const that had been passed as a const reference or pointer. In both cases, the user would have to cast away const first. The implication of this view is that casting away const for an object that was originally defined const and then writing to it is at best undefined, whereas doing the same to an object that wasn't originally defined const is legal and well defined.
Note that with this refinement of the rules, the meaning of const doesn't depend on whether a type has a constructor or not; in principle, they all do. Any object declared const now may be placed in ROM, be placed in code segments, be protected by access control, etc., to ensure that it doesn't mutate after receiving its initial value. Such protection is not required, however, because current systems cannot in general protect every const from every form of corruption.
To elaborate on what Jerry Coffin said: the standard makes accessing a const object undefined, only if that access occurs during the object's lifetime.
7.1.5.1/4:
Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const object during its lifetime (3.8) results in undefined behavior.
The object's lifetime begins only after the constructor has finished.
3.8/1:
The lifetime of an object of type T begins when:
storage with the proper alignment and size for type T is obtained, and
if T is a class type with a non-trivial constructor (12.1), the constructor call has completed.
The standard doesn't really say a lot about how the implementation makes it work, but the basic idea is pretty simple: the const applies to the object, not (necessarily) to the memory in which the object is stored. Since the ctor is part of what creates the object, it's not really an object until (sometime shortly after) the ctor returns. Likewise, since the dtor takes part in destroying the object, it's no longer really operating on a complete object either.
Here's a way that ignoring the standard could lead to incorrect behavior. Consider a situation like this:
class Value
{
int value;
public:
value(int initial_value = 0)
: value(initial_value)
{
}
void set(int new_value)
{
value = new_value;
}
int get() const
{
return value;
}
}
void cheat(const Value &v);
int doit()
{
const Value v(5);
cheat(v);
return v.get();
}
If optimized, the compiler knows that v is const so could replace the call to v.get() with 5.
But let's say in a different translation unit, you've defined cheat() like this:
void cheat(const Value &cv)
{
Value &v = const_cast<Value &>(cv);
v.set(v.get() + 2);
}
So while on most platforms this will run, the behavior could change depending on what the optimizer does.
Constness for a user-defined type is different than constness for a built-in type. Constness when used with user-defined types is said to be "logical constness." The compiler enforces that only member functions declared "const" can be called on a const object (or pointer, or reference). The compiler cannot allocate the object in some read-only memory area, because non-const member functions must be able to modify the object's state (and even const member functions must be able to when a member variable is declared mutable).
For built-in types, I believe the compiler is allowed to allocate the object in read-only memory (if the platform supports such a thing). Thus casting away the const and modifying the variable could result in a run-time memory protection fault.