Looking at the following example. Does the C++ standard guarantee that the value of object.x
will be equal to 1
at the end? What if I don't call the destructor object.~Class();
?
#include <new>
class Class
{
public:
Class() {}
~Class() {}
int x;
};
int main()
{
Class object;
object.x = 1;
object.~Class();
new (&object) Class();
object.x == ?
Class object_2;
object_2.x = 1;
new (&object_2) Class();
object_2.x == ?
return 0;
}
Placement new is a variation new operator in C++. Normal new operator does two things : (1) Allocates memory (2) Constructs an object in allocated memory. Placement new allows us to separate above two things. In placement new, we can pass a preallocated memory and construct an object in the passed memory.
std::vector<T,Allocator>::emplace_back Appends a new element to the end of the container. The element is constructed through std::allocator_traits::construct, which typically uses placement-new to construct the element in-place at the location provided by the container.
¶ Δ Probably not. Unless you used placement new , you should simply delete the object rather than explicitly calling the destructor.
A placement new expression first calls the placement operator new function, then calls the constructor of the object upon the raw storage returned from the allocator function.
No.
The object whose x
is equal to 1
was destroyed.
You know that, because you're the one who destroyed it.
Your new x
is uninitialised and has an unspecified value, which may be 1
in practice due to memory re-use. That's no different than for any other uninitialised value.
Since there seems to be a lot of people throwing about assertions, here are some facts, direct from the standard.
There is no direct statement about this case because it follows from the general rules governing what objects are and what object lifetime means.
Generally, once you've grokked that C++ is an abstraction rather than a direct mapping of bytes, you can understand what's going on here, and why there is no such guarantee as that the OP seeks.
First, some background on object lifetime and destruction:
[C++14: 12.4/5]:
A destructor is trivial if it is not user-provided and if:
- the destructor is not
virtual
,- all of the direct base classes of its class have trivial destructors, and
- for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.
Otherwise, the destructor is non-trivial.
[C++14: 3.8]
: [..] The lifetime of an object of typeT
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.
[C++14: 3.8/3]
: The properties ascribed to objects throughout this International Standard apply for a given object only during its lifetime. [..]
[C++14: 3.8/4]
: 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 (5.3.5) 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.
(Most of this passage is irrelevant for your first case, as it does have an explicit destructor call: your second violates the rules in this paragraph and thus has undefined behaviour; it shall not be discussed further.)
[C++14: 12.4/2]
: A destructor is used to destroy objects of its class type. [..]
[C++14: 12.4/11]
: [..] Destructors can also be invoked explicitly.
[C++14: 12.4/15]
: Once a destructor is invoked for an object, the object no longer exists. [..]
Now, some specifics. What if we were to inspect object.x
before the placement-new?
[C++14: 12.7/1]
: [..] 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.
Wow, okay.
And what if we were to inspect it after the placement-new? i.e. what value does the x
in the new object hold? Is it guaranteed, as the question asks, that it'll be 1
? Bear in mind that Class
's constructor includes no initialiser for x
:
[C++14: 5.3.4/17]:
A new-expression that creates an object of typeT
initializes that object as follows:
- If the new-initializer is omitted, the object is default-initialized (8.5); if no initialization is performed, the object has indeterminate value.
- Otherwise, the new-initializer is interpreted according to the initialization rules of 8.5 for direct-initialization.
[C++14: 8.5/16]:
The initialization that occurs in the formsT x(a); T x{a};
as well as in
new
expressions (5.3.4),static_cast
expressions (5.2.9), functional notation type conversions (5.2.3), and base and member initializers (12.6.2) is called direct-initialization.
[C++14: 8.5/17]:
The semantics of initializers are as follows. The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly parenthesized) expression, the source type is not defined. [..]
- If the initializer is
()
, the object is value-initialized.- [..]
[C++14: 8.5/8]:
To value-initialize an object of typeT
means:
- if T is a (possibly cv-qualified) class type (Clause 9) with either no default constructor (12.1) or a default constructor that is user-provided or deleted, then the object is default-initialized.
- [..]
[C++14: 8.5/7]:
To default-initialize an object of typeT
means:
- if
T
is a (possibly cv-qualified) class type (Clause 9), the default constructor (12.1) forT
is called (and the initialization is ill-formed ifT
has no default constructor or overload resolution (13.3) results in an ambiguity or in a function that is deleted or inaccessible from the context of the initialization);- if
T
is an array type, each element is default-initialized; — otherwise, no initialization is performed.If a program calls for the default initialization of an object of a const-qualified type
T
,T
shall be a class type with a user-provided default constructor.
[C++14: 12.6.2/8]:
In a non-delegating constructor, if a given non-static data member or base class is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer) and the entity is not a virtual base class of an abstract class (10.4), then:
- if the entity is a non-static data member that has a brace-or-equal-initializer, the entity is initialized as specified in 8.5;
- otherwise, if the entity is an anonymous union or a variant member (9.5), no initialization is performed;
- otherwise, the entity is default-initialized (8.5).
[C++14: 8.5/12]:
If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, an object with automatic or dynamic storage duration has indeterminate value.
So, does the standard guarantee that your replacement object's x
has value 1
? No. It does not.
In practice, why might it not be? Well, any number of reasons. Class
's destructor is non-trivial so, per 3.8, the first object's lifetime has ended immediately after you called its destructor.
Technically, the compiler is then free to place an object at that location as long as it's destroyed by the time the placement-new takes effect. There's no reason for it to do so here, but there's nothing prohibiting it; more importantly, a simple { int x = 5; x = 42; }
in between the destructor call and the placement-new would be more than entitled to re-use that memory. It's not being used to represent any object at that point!
More realistically, there are implementations (e.g. Microsoft Visual Studio) that, for debug-mode programs, write a recognisable bit-pattern into unused stack memory to aid in diagnosing program faults. There's no reason to think that such an implementation wouldn't hook into destructors in order to do that, and such an implementation would be overwriting your 1
value. There is nothing in the standard prohibiting this whatsoever.
Indeed, if I replace the ?
lines in your code with std::cout
statements, so that we actually inspect the values, I get a warning about an uninitialised variable and a value 0
in the case where you used a destructor:
main.cpp: In function 'int main()':
main.cpp:19:23: warning: 'object.Class::x' is used uninitialized in this function [-Wuninitialized]
std::cout << object.x << '\n';
^
0
1
I'm unsure how much more proof you need that the standard does not guarantee a 1
value here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With