How do I use lambda with temporary object implementing move semantics?

85 Views Asked by At

I am trying to write a lambda that receives a temporary object.
The lambda will be cached for later execution, and will be executed after the original object was destructed (hence should make a copy if it).

Main points:

  1. Define class X that implements move semantics, but disable copy-c'tor and assignment op,

  2. Define function foo(X x) that receives this type,

  3. Create lambda l, and store it general function-pointer type std::function<void()>, for later execution. We will call this function-pointer f,

  4. After original x was destructed, execute f.

I did not manage to write this lambda.
I know I can solve this easily using pointer to object instead of object, but I want to learn whether it is possible to use the object directly, assuming I implemented all move semantics correctly.

Code example (which does not work correctly):

#include <iostream>
#include <string>
#include <functional>

class X
{
private:
    std::string m_s;

public:
    X(){}

    X(X&& other)
    { m_s = std::move(other.m_s);}

    ~X()
    { m_s = "Object destructed...";}
    
    X& operator=(X&& other)
    { m_s = std::move(other.m_s); return *this;}

    void SetString(const char* s)
    { m_s = s;}
    
    void Print() const
    { ::printf("X::Print() - String value: '%s'\n", m_s.c_str());}

private:
    X(const X&) = delete;
    X& operator=(const X&) = delete;
};

void foo(X x)
{ x.Print();}

//  Predefined lambda:
auto l = [](X x){ foo(std::move(x));};

int main()
{
    std::function<void()> f = nullptr;

    //  Create temporary scope for 'x' to verify object 
    //  destruction happens before 'f' is called:
    {
        X x;
        x.SetString("Hello World!");

    
        f = [&](){ l(std::move(x));};
    }

    //  Expecting to see 'Hello World!'
    f();

    return 0;
}
1

There are 1 best solutions below

8
Paul Sanders On

OK, I've got my head around this now so I've rewritten my answer. There's no way to make this work with x declared on the stack because how ever much moving you do, the parameter passed to foo will have been destroyed by the time the lambda actually executes.

Instead (and it's obvious, once you see it), x has to be dynamically allocated as this is the only way to manage its lifetime. Given that observation, it becomes clear how to pass it around: there's a smart pointer for that, it's copyable (deliberately) and it's called std::shared_ptr. So here's code that works correctly:

#include <iostream>
#include <string>
#include <functional>
#include <memory>

class X
{
    std::string m_s;

public:
    X () { }
    X (std::string s) { m_s = s; }
    ~X () { m_s = "destroyed"; std::cout << m_s << "\n"; }
    X (const X&) = delete;
    X& operator= (const X&) = delete;
    X (X&& other) { m_s = other.m_s; other.m_s.clear (); }
    X& operator= (X&& other) { m_s = other.m_s; other.m_s.clear (); return *this; };
    inline void Print() const { std::cout << m_s << "\n"; }
};

void foo (std::shared_ptr <X> x) { x->Print (); }

int main()
{
    std::function <void ()> f;

    {
        auto x = std::make_shared <X> ("Hello World!");
        f = [=] () { foo (x); };
    }

    f ();
}

Output:

Hello World! destroyed

Live demo (Wandbox appears to be broken :( )

And OP, if you post what you claim is a MRE, please:

  • make sure it compiles

  • include all the relevant #includes

  • include all the relevant code (where are the [deleted] copy and [implemented] move constructors / operators?)

The first part of the question (which, on reflection, I agree you need to keep) should also be distilled down into a single code fragment. Thank you.