Reputation: 898
Say there is an object with automatic storage duration, and that object is copy-initialized from within a nested scope, like a loop body. Is the lifetime of the values created from within the nested scope extended into the containing scope?
#include<iostream>
using namespace std;
class Thing {
public:
int data;
Thing(int data) : data(data) { cout << "making a thing" << endl; }
~Thing() { cout << "destroying a thing" << endl; }
};
int main() {
Thing t = Thing(-1);
for (int i = 0; i < 4; i++) {
t = Thing(i); // this is both created AND destroyed from within this scope...?
}
cout << t.data << endl; // undefined behavior?
}
Right now, accessing t.data
at the end works, but I see that the destructor for each Thing
is invoked once per loop iteration, so I might just be getting lucky?
This looks relevant (but I am not a lawyer so it's tough to decipher): some 2011 c++ standard
specifically:
- For such an object [with automatic storage duration] that does not have a variable length array type, its lifetime extends from entry into the block with which it is associated until execution of that block ends in any way...
So if my code snippet is undefined behavior - for a loop body to change a local variable, that local variable should be manually heap allocated and later manually released, or...?
Upvotes: 2
Views: 113
Reputation: 1941
There is no undefined behavior here:
#include<iostream>
class Thing {
public:
int data;
Thing(int data) : data(data) { std::cout << "making a thing" << data << std::endl; }
~Thing() { std::cout << "destroying a thing" << data << std::endl; }
Thing& operator=(const Thing &t) { data = t.data; std::cout << "operator=" << std::endl; return *this; }
};
int main() {
Thing t = Thing(-1); // create object t
for (int i = 0; i < 4; i++) {
t = Thing(i);
// 1. create an object Thing(i)
// 2. use operator= to copy that object into the object t
// 3. Thing(i) object gets destroyed at the end of scope
}
// object t is still valid, but it was modified in the loop with operator=
cout << t.data << endl; // no undefined behavior here
}
You can add copy-assignment operator (operator=
) member function into the Thing class to check this. In your case this operator was implicitly added by the compiler, according to the rules in the standard:
If no user-defined copy assignment operators are provided for a class type (struct, class, or union), the compiler will always declare one as an inline public member of the class. This implicitly-declared copy assignment operator has the form T& T::operator=(const T&) if all of the following is true:
- each direct base B of T has a copy assignment operator whose parameters are B or const B& or const volatile B&
- each non-static data member M of T of class type or array of class type has a copy assignment operator whose parameters are M or const M& or const volatile M&
Upvotes: 1
Reputation: 155497
This is perfectly legal, defined behavior.
Your mistake is in thinking that the lifetime of the Thing
created inside the block matters. It doesn't. After the copy, the copy-assigned t
hasn't changed what it is; it's still the same t
created outside the block, it just had its values updated. Its lifetime is entirely unaffected. In the context of your quote, the "block with which it[t
] is associated" is the top-level function scope (where it was declared), not the block in which it was copy-assigned.
The Thing
created inside the block does expire each time, but that's fine; it was copied from, then never used again.
Upvotes: 6