Home > Mobile >  Overwriting object with new object of same type and using closure using this
Overwriting object with new object of same type and using closure using this

Time:03-05

In the following code an object is overwritten with a new object of same type, where a lambda-expression creates a closure that uses this of the old object. The old address (this) remains the same, the new object has the same layout, so this should be ok and not UB. But what about non trivial objects or other cases?

struct A {
    void g(A& o, int v) {
        o = A{.x = v, .f = [this]{
            std::cout << "f" << this->x << '\n';
        }};
    }
    int x{0};
    std::function<void()> f;
    ~A() {
        std::cout << "dtor" << x << '\n';
    }
};

void test() {
    A a;
    a.g(a, 2);
    a.f();
}

CodePudding user response:

You are not actually replacing any object. You are just assigning from another object to the current one. o = simply calls the implicit copy assignment operator which will copy-assign the individual members from the temporary A constructed in the assignment expression with A{...}.

The lambda is going to capture this from this in g, not from the temporary object.

std::function will always keep a copy of the lambda referring to the original object on which g was called and since that is its parent object, it cannot outlive it.

So there is no problem here. The only exception would be that you call f during the destruction of the A object, in which case using the captured pointer may be forbidden.

CodePudding user response:

Here is a slightly modified code with a corner case. I create a temporary in a function and call g on it passing it a more permanent object. The temporary vanishes and the long life object now has a closure refering to an object after its end of life. Invoking f is UB:

#include <iostream>
#include <functional>

struct A {
    void g(A& o, int v) {
        o = A{ .x = v, .f = [this] {
            std::cout << "f" << this->x << ' ' << this << '\n';
        } };
    }
    int x{ 0 };
    std::function<void()> f;
    ~A() {
        std::cout << "dtor" << x <<  ' ' << this << '\n';
    }
};

void test(A& a) {
    A b{ 2 };
    b.g(a, 3);
}

int main() {
    A a{ 1 };
    std::cout << a.x << '\n';
    test(a);
    std::cout << a.x << '\n';
    a.f();   // UB because a.f uses an object after its end of life
}

The output is:

1
dtor3 0135F9C0
dtor2 0135FA30
3
f341072 0135FA30
dtor3 0135FAA8

proving that the invocation of a.f() tried to use the object at address 0135FA30 (in that specific run) after it has been destroyed.

  • Related