Constructing lambdas in constructors

80 Views Asked by At

I had some nasty bugs when constructing lambdas in constructors when working with callback-based event system. Here's simple scenario:

class Object {
public:
    int variable = 10;
    std::function<void()> callback;

    Object() {
        callback = [this]() {
            variable += 10;
        };
    }
};

class ObjectReceiver {
public:
    Object object;

    explicit ObjectReceiver(Object &&hi) : object(std::move(hi)) {}
};

int main() {
    ObjectReceiver receiver{Object()};
    receiver.object.callback();
    std::cout << receiver.object.variable << std::endl;
    return 0;
}

Here, "10" will be printed, because the object was moved, and pointer that lambda is holding, invalidated.

This means that in code outside of Object, i should always care, whether it constructs lambda in it's constructor.

How to deal with such problems? Should i make general convention for myself to not construct lambdas from constructors?

1

There are 1 best solutions below

0
Jonno On

The problem is that the call receiver.object.callback(); is operating with an argument this that points to the temporary instance of Object created by the constructor Object() in ObjectReceiver receiver{Object()};. Even if you pass everything by value and avoid move semantics altogether you will still have the same problem. The underlying issue is that this is passed by value and not updated when move semantics are applied to copying the temporary instance of Object to the instance stored in receiver.object.

To make it work, you need to make the lambda relative to the instance of Object to which it applies with something like:

class Object {
public:
    int variable = 10;
    std::function<void(Object& o)> raw_callback;
    void callback() { raw_callback (*this); }

    Object() {
        raw_callback = [](Object& o) {
            o.variable += 10;
        };
    }
};