0

I'm trying to use lambdas with threads in c++, and use captures as a way of passing data to the thread. The following code prints "ConnectionInfo(copy)" three times when the thread is created. Is there anyway to reduce it to a single copy? I'm not sure why it's copied the extra two times.

#include <iostream>
#include <functional>
#include <memory>
#include <thread>

using namespace std;

class ConnectionInfo
{
public:
    ConnectionInfo():port(0) {}
    ConnectionInfo(int p):port(p) {}
    ConnectionInfo(const ConnectionInfo &other) 
    {
        std::cout << "ConnectionInfo(copy)" << std::endl;
        port = other.port;
    }

    int port;
    void Connect() {};

};


int main() {

    std::cout << "Create" << std::endl;
    ConnectionInfo c(2);


    std::cout << "Thread" << std::endl;
    std::thread t(
    [a = c]() mutable
    { 
        a.Connect();
        std::cout << "Done" << std::endl;
    }
    );

    std::cout << "Joining" << std::endl;
    t.join();
    std::cout << "Joined" << std::endl;

    return 0;
}

Output is:

Create
Thread
ConnectionInfo(copy)
ConnectionInfo(copy)
ConnectionInfo(copy)
Joining
Done
Joined
bpeikes
  • 3,495
  • 9
  • 42
  • 80
  • 3
    You're currently capturing `c` by value. Do you want all threads to share a single `c`? If so, you should capture it by reference: `[&a = c]{ ... }` – alter_igel Jul 16 '18 at 18:50
  • There's a copy to capture it into the lambda (`[a = c]`), a copy when the lambda is copied into the `std::thread`, and one other copy that I'm not 100% on the source of, but I think is the lambda being copied over to the actual thread. – Justin Jul 16 '18 at 18:51
  • 1
    Perhaps [this question](https://stackoverflow.com/questions/8640393/move-capture-in-lambda) can help. – François Andrieux Jul 16 '18 at 18:55
  • I want the thread to have its own copy of c, because caller might go away, and call detach on thread. – bpeikes Jul 17 '18 at 01:35
  • What compiler/standard library are you using? – MikeMB Jul 17 '18 at 06:46

1 Answers1

2

The definition of std::thread requires the arguments to be passed by value to the new thread and as the lambda is a temporary in your case, that is exactly what you want. However, those extra copies are most likely actually moves that degrade to copies, as your class doesn't have a move constructor.

So, adding a move constructor to your class, should bring down the sequence to one copy (the capture) and two moves.

Of course, this only helps, if move is cheaper than copy, but i if the overhead of a copy matters compared to the creation of the thread, your object is most likely managing some data on the heap or has to perform otherwise expensive operations on copy that often can be omitted in the move constructor.

MikeMB
  • 20,029
  • 9
  • 57
  • 102
  • Thanks. That is exactly what is going on. Adding a move constructor, as well as using c++14 generalized lambda capture changes it to three moves instead of copies. Hopefully the optimizer would be able to consolidate them. – bpeikes Jul 17 '18 at 13:24