Is it possible for a class to contain a lambda object that captures the class its in?

Viewed 73

Let's just say I have a Window class, and that Window class has event handlers in the form of std::function:

struct Event {};
struct Window
{
    std::function<void(Event)> handler;
};

Window wnd;
wnd.handler = [] (Event) { /*some fancy handler*/ };

From inside the handler I don't have access to the window itself, so I can either pass the window pointer to the handler, or I can implicitly have access inside the handler function to the window by capturing it. However in the capturing case I think I run into this problem:

struct Event {};
struct Window
{
    std::function<void(Event)> handler;
    int member;
};

int main()
{
    Window wnd1;
    
// Capture the window so I can access it inside the handler function
    Window wnd2{ [&wnd2](Event) { wnd2.member = 3; } };
    
    wnd1 = std::move(wnd2);
    // wnd1 contains a lambda object pointing to wnd2, that's invalid.
    
    Window wnd3 { [&wnd3](int) { wnd3.member = 7; } };
    std::vector<Window> wnd_vector{ wnd3 }; // Now when vector reallocates the lambda // object is invalid because of the 'this' pointer, right?

So then the rule I guess is: You can't have a class containing a lambda object if that lambda object has captured the outer class (IF the outer class is movable). I think this problem extends to more general cases:

struct MoveableClass
{
    struct Foo { MoveableClass* outer_class_object; }; 
    Foo fooObj{this};
}

I can handle moving this class by using custom move functions. But in the case of std::function or if it contains a lambda, then I can't move it, right?

1 Answers

I propose you such solution:

#include <iostream>
#include <vector>
#include <functional>

struct Event {};
struct Window
{
public:
    Window() = default;
    Window(std::function<void(Window &, Event)> handler)
      : handler(handler)
    {}
    Window(std::function<void(Window &, Event)> handler,
           int member)
      : handler(handler),
        member(member)
    {}

    void setHandler(std::function<void(Window &, Event)> newHandler)
    {
        handler = newHandler;
    }
    bool callHandler(Event event)
    {
        if (handler)
        {
            handler(*this, event);
            return true;
        }
        return false;
    }
    
    void setMember(int newMember)
    {
        member = newMember;
    }
    int getMember() const
    {
        return member;
    }
private:
    std::function<void(Window &, Event)> handler; // if it is not needed to change class members than you can use "const Window &"
    int member = 0;
};

void print(const Window & obj)
{
    std::cout << "Object: " << static_cast<const void *>(&obj) << ", member: " << obj.getMember() << std::endl;
}

int main()
{
    Event event;
    bool callResult = false;
    
    Window wnd1;
    
    Window wnd2{ [](Window & obj, Event) { obj.setMember(3); } };
    callResult = wnd2.callHandler(event);
    std::cout << "Call result: " << std::boolalpha << callResult << std::endl;
    print(wnd2);
    wnd2.setHandler( [](Window & obj, Event) { obj.setMember(1); } );
    
    
    wnd1 = std::move(wnd2);
    callResult = wnd1.callHandler(event);
    std::cout << "Call result: " << std::boolalpha << callResult << std::endl;
    print(wnd1);
    
    Window wnd3 { [](Window & obj, Event) { obj.setMember(7); } };
    std::vector<Window> wnd_vector{ wnd3 };
    callResult = wnd_vector.front().callHandler(event);
    std::cout << "Call result: " << std::boolalpha << callResult << std::endl;
    print(wnd_vector.front());
    print(wnd3);
    return 0;
}

Result:

Call result: true
Object: 0x7ffd57d9ccb0, member: 3
Call result: true
Object: 0x7ffd57d9cc80, member: 1
Call result: true
Object: 0x2263c30, member: 7
Object: 0x7ffd57d9cce0, member: 0
Related