4
votes

When I am constructing std::function with lambda with captured values it makes an additional copy (move) of those parameters (actually the of the whole lambda object I guess). The code:

#include <iostream>
#include <functional>

// Testing class - just to see constructing/destructing.
class T {
private:
    static int idCounter; // The global counter of the constructed objects of this type.
public:
    const int id; // Unique object ID 

    inline T() : id(++idCounter) { 
        std::cout << "  Constuctor Id=" << id << std::endl;
    };
    inline T(const T& src) : id(++idCounter) {
        std::cout << "  Copy constructor Id=" << id << std::endl;
    }
    inline T(const T&& src) : id(++idCounter) {
        std::cout << "  Move constructor Id=" << id  << std::endl;
    }
    inline void print() const {
        std::cout << "  Print is called for object with id=" << id << std::endl;
    }
    inline ~T() {
        std::cout << "  Destructor Id=" << id << std::endl;
    }
};

int T::idCounter=0; 

// Declare type of the std::function to store our lambda.
typedef std::function<int (void)> Callback;

int main()
{ 
    std::cout << "Let's the game begin!" << std::endl;
    T obj; // Custruct the first object.
    std::cout << "Let's create a pointer to the lambda." << std::endl;
    // Make a labmda with captured object. (The labmda prints and returns object's id).
    // It should make one (local) copy of the captured object but it makes it twice - why?!
    const Callback* pcb= new Callback( [obj]() -> int { 
        obj.print();
        return obj.id; 
    } );
    std::cout << "Now let's print lambda execution result."  << std::endl;
    std::cout << "The functor's id is " << (*pcb)() << std::endl;
    std::cout << "Destroying the lambda." << std::endl;
    delete pcb;
    std::cout << "Terminating." << std::endl;
    return 0;

}

The output is:

  Let's the game begin!
   Constuctor Id=1
  Let's create a pointer to the lambda.
   Copy constructor Id=2
   Move constructor Id=3
  Destructor Id=2
   Now let's print lambda execution result.
   Print is called for object with id=3
   The functor's id is 3
  Destroying the lambda.
   Destructor Id=3
  Terminating.
   Destructor Id=1
  

I made a std:function with lambda with captured object. It should make a local copy of the object for lambda but it make the copy twice (look at move constructor call - highlighted with bold). Actually it make a copy of the whole lambda object. Why? How can I avoid that? I am using lambdas for inter-thread event processing and they may capture noticeable amounts of date so I am trying to find a way to avoid unnecessary copying. So the task is simple - to pass constructed lambda into the function with minimal expenses - if it will copy data twice for every constructed lambda I would search for another way to work with events.
I am using GCC v4.7.2 forced to GNU C++11.

3
The move is done when moving the lambda in the initialization-list of the constructor of std::function. This moving-the-lambda forces the captured object to move as well (i.e recursively moving!)>Nawaz
@op, moving is not copying (of course you can implement it like that, but why would you?). A sensible implementation for your test class would be to not increment the id but instead take the id of the moved (temporary) object to the new instance.Tamás Szelei
In real life in complex project you can't guarantee the moving is cheap. You are using third-party libraries, multithreading issues etc. As an example - is moving of sdt:vector with 10k string cheap?Sap
@user3544995 Yes, it is. Roughly four pointer assignments.pmr

3 Answers

3
votes

Well, the output is confusing because there is one copy-elision performed by the compiler. So in order to understand the behaviour, we need to disable the copy-elision for a while. Use -fno-elide-constructors flag when compiling the code:

$ g++ -std=c++11 -fno-elide-constructors main.cpp

Now it gives this output (demo-without-copy-elision):

Let's create a pointer to the lambda.
  Copy constructor Id=2
  Move constructor Id=3
  Move constructor Id=4
  Destructor Id=3
  Destructor Id=2

Well, that is expected. The copy is done when creating the lambda:

 [obj]() -> int { 

//^^^^ COPY!

    obj.print();
    return obj.id; 
}

Well, that is too obvious!

Now coming to the non-obvious thing : the two move operations!

The first move is done when passing the lambda to the constructor of std::function, because the lambda is an rvalue, hence move-constructor is called. Note that -fno-elide-constructors disables move-elision also (which is just a supposedly faster version of copy, after all!).

The second move is done, when writing (by moving of course) to the member data of std::function in the constructor initialization-list.

So far so good.

Now if you remove -fno-elide-constructors, the compiler optimizes away the first move (because of which it doesn't invoke the move constructor), which is why the output is this:

Let's create a pointer to the lambda.
  Copy constructor Id=2
  Move constructor Id=3
  Destructor Id=2

See demo-with-copy-elision.

Now the move you see now, is because of moving-the-lambda into the member data of std::function. You cannot avoid this move.

Also note that copying/moving the lambda also causes copying/moving the captured data (i.e recursively copying/moving).

Anyway, if you're worrying about copying the captured object (assuming it is a huge object), then I would suggest you to create the captured object using new so that copying the captured object means copying a pointer (4 or 8 bytes!). That should work great!

Hope that helps.

2
votes

It does not make copy twice. Moving is considered a cheap operation, and practically in 99% of the cases it is. For 'plan old data' types (structs, ints, doubles, ...) the double-copying is a non-issue as most compilers eliminate redundant copies (data-flow analysis). For containers, moving is a very cheap operation.

0
votes

As mentioned by Nawaz in the comments, the extra move operation that you are worried about is performed when the lambda expression is moved into the std::function<int(void)> (typedef'ed as Callback).

const Callback* pcb= new Callback( [obj]() -> int { 
    obj.print();
    return obj.id; 
} );

Here the object obj is passed by value (copy constructed) to the lambda expression, but additionally, the entire lambda expression is passed as an r-value to the constructor of Callback (std::function) and is therefore move-copied into the std::function object. When moving the lambda, all states must also be moved along and hence the obj is also moved (there are actually two move constructions of obj involved but one of them is usually optimized out by the compiler).

Equivalent code:

auto lambda = [obj]() -> int {                        // Copy obj into lambda.
    obj.print();
    return obj.id; 
};

const Callback* pcb= new Callback(std::move(lambda)); // Move lambda (and obj).

Move operations are considered cheap and won't cause any costly copying of data (in most cases).

You can read more about move semantics here: What are move semantics?.

Finally If you don't want to copy obj then simply capture it by reference in the lambda:

const Callback* pcb= new Callback( [&obj]() -> int { 
    obj.print();
    return obj.id; 
} );