DWR
DWR

Reputation: 898

lifetime of automatic storage that enters a nested scope

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:

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

Answers (2)

Vasilij
Vasilij

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:

  1. each direct base B of T has a copy assignment operator whose parameters are B or const B& or const volatile B&
  2. 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

ShadowRanger
ShadowRanger

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

Related Questions