Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Object lifetime, in which situation is reused the storage?

In the C++ ISO standard N4618 (but it almost also applies to the C++11 version) one can read:

at §1.8 The C++ Object Model:

If a complete object is created (5.3.4) in storage associated with another object e of type “array of N unsigned char”, that array provides storage for the created object... [Note:If that portion of the array previously provided storage for another object, the lifetime of that object ends because its storage was reused]

=> OK, array of unsigned char can provide storage for other object, If a new object occupies storage which was previously occupied by an other object, the new objects reuses the storage of the previous.

at §3.8.8 object lifetime

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,...

=> I can construct an object at the storage location of an other object but this operation is not a "storage reuses" (Otherwise why would it be written ...before the storage which the object occupied is resused...)

And as an example of §3.8.8

struct C {
 int i;
 void f();
 const C& operator=( const C& );
};

const C& C::operator=( const C& other) {
  if ( this != &other ) {
    this->~C();          // lifetime of *this ends
    new (this) C(other); // new object of type C created
    f();                 // well-defined
  }
  return *this;
}

C c1;
C c2;
c1 = c2;  // well-defined
c1.f();  // well-defined; c1 refers to a new object of type C

So in this example new(this) C(other) would not be a storage reuse because c1 has automatic storage duration.

On the contrary in this example:

alignas(C) unsigned char a[sizeof(C)];
auto pc1 = new (&a) C{};
C c2;
*pc1 = c2;

the expression new (this) C(other) evaluated during the assignment *pc1=c2 is a storage reuse since the object pointed to by pc1 has storage provided by an unsigned char array.

Are the following assertions (and the previouses) right:

  • §3.8.8 does not applies if the initial object is constructed on a storage provided by an unsigned char array;
  • the term "storage reused" only applies for storage provided by unsigned char array.

EDIT: Ok please do not focus on the "storage reuses" term and focus on the question "§3.8.8 does not applies if the initial object is constructed on a storage provided by an unsigned char array"?

Because if it is not the case, so all std::vector implementation I know are not correct. Indeed they save the allocated storage in a pointer of type value_type called __begin_ for example. Let's say you make a push_back on this vector. The object will be created at the begining of the allocated storage:

 new (__begin_) value_type(data);

Then you make a clear, which will call the destroy of the allocator which will call the destructor of the object:

 __begin_->~value_type();

Then if you make a new push_back, the vector won't allocate new storage:

new (__begin_) value_type(data);

Thus according de §3.8.8 if value_type has a ref data member or const data member, then a call to front which result in *__begin_ will not point to the new pushed object.

So I think that storage reuses has in $3.8.8 a special meaning otherwise, std library implementor are wrong? I have checked libstdc++ et libc++ (GCC and Clang).

This what would happen in this example:

 #include <vector>
 struct A{
   const int i;
 };
 int main() {
   std::vector<A> v{};
   A a{};
   v.push_back(A{});
   v.clear();
   v.push_back(A{2});
   return 0;
 }
like image 429
Oliv Avatar asked Jan 16 '17 14:01

Oliv


People also ask

What is the lifetime of the object?

In object-oriented programming (OOP), the object lifetime (or life cycle) of an object is the time between an object's creation and its destruction.

How can you extend the lifetime of an object?

The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details.

What is the life time of local object?

The lifetime of a variable is the time during which the variable stays in memory and is therefore accessible during program execution. The variables that are local to a method are created the moment the method is activated (exactly as formal parameters) and are destroyed when the activation of the method terminates.

What is the lifetime of local object in C Plus Plus?

The lifetime of an object begins when its initialization is complete, and ends when its storage is released. Dynamic storage duration starts when the storage created by (new Type) is initialized, and ends when the object goes out of scope or is deleted by “delete pointer”.


1 Answers

=> OK, array of unsigned char can provide storage for other object, If a new object occupies storage which was previously occupied by an other object, the new objects reuses the storage of the previous.

Correct, but for the wrong reasons.

The notation you cite is non-normative text. That's why it appears in the "[note: ...]" markup. Non-normative text has no weight when deciding what the standard actually says. So you cannot use that text to prove that constructing an object in an unsigned char[] constitutes storage reuse.

So if indeed it does constitute storage reuse, that is only because "reuse" is defined by plain English, not because the standard has a rule explicitly defining this as one of the cases of "storage reuse".

I can construct an object at the storage location of an other object but this operation is not a "storage reuses" (Otherwise why would it be written ...before the storage which the object occupied is resused...)

No. [basic.life]/8 is trying to explain how you can use pointers/references/variable names to an object after that object's lifetime has ended. It explains the circumstances under which those pointers/references/variable names are still valid and can access the new object created in its storage.

But let's dissect the wording:

If, after the lifetime of an object has ended

OK, so we have this situation:

auto t = new T;
t->~T(); //Lifetime has ended.

and before the storage which the object occupied is reused or released

And neither of the following has happened yet:

delete t; //Release storage. UB due to double destructor call anyway.
new(t) T; //Reuse the storage.

a new object is created at the storage location which the original object occupied

Therefore, we do this:

new(t) T; //Reuse the storage.

Now, that sounds like a contradiction, but it isn't. The "before storage gets reused" part is intending to prevent this:

auto t = new T;  //Storage created, lifetime begun.
t->~T(); //Lifetime has ended; storage not released.
new(t) T; //[basic.life]/8 applies, since storage hasn't been reused yet.
new(t) T; //[basic.life]/8 does not apply, since storage was just reused.

[basic.life]/8 is saying that the paragraph does not apply if you created a new object between the previous object's destruction and your attempt to create a new object. That is, [basic.life]/8 doesn't apply if you double reuse storage.

But the act of creating the new object is still reusing the storage. Storage reuse is not a fancy C++ term; it's just plain English. It means exactly what it sounds like: storage was used for object A, now you reuse that same storage for object B.


EDIT: Ok please do not focus on the "storage reuses" term and focus on the question "§3.8.8 does not applies if the initial object is constructed on a storage provided by an unsigned char array"?

But... it does apply.

vector stores a pointer to the first element. That object gets allocated and constructed. Then the destructor gets called, but the storage remains. Then the storage gets reused.

That is the exact case that [basic.life]/8 is talking about. The new object being created is the same type as the old one. The new object overlays the storage for the old one exactly. The objects cannot be base subobjects of anything, by the nature of vector. vector doesn't let you stick const-qualified objects in itself.

[basic.life]/8's protections very much do apply: the new object can be accessed via pointers/references to the old one. So unless you do a lot of copy/move constructor/assignment work to put types with const or reference members in vector, it will work.

And even that last case can be satisfied by implementations laundering their pointers. Oh, and launder is new, from C++17. C++14 has no provisions for what to do with types where [basic.life]/8 doesn't apply.

like image 131
Nicol Bolas Avatar answered Oct 01 '22 07:10

Nicol Bolas