34

Well I have an issue with passing data into a thread using std::thread. I thought I understood the general semantics of copy constructors, etc. but it seems I don't quite grasp the problem. I have a simple class called Log that has hidden it's copy constructor thusly:

class Log
{
public:
    Log(const char filename[], const bool outputToConsole = false);
    virtual ~Log(void);

    //modify behavior
    void appendStream(std::ostream *);
    //commit a new message
    void commitStatus(const std::string str);

private:
    //members
    std::ofstream fileStream;
    std::list<std::ostream *> listOfStreams;

    //disable copy constructor and assignment operator
    Log(const Log &);
    Log & operator=(const Log &);
}

now I have a main based heavily on http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio/example/cpp11/echo/blocking_tcp_echo_server.cpp

int main()
{
    static int portNumber = 10000;

    Log logger("ServerLog.txt", true);
    logger.commitStatus("Log Test String");

    try {
        boost::asio::io_service ioService;
        server(ioService, portNumber, logger);
    }
    catch (std::exception &e)
    {
        std::cerr << "Exception " << e.what() << std::endl;
        logger.commitStatus(e.what());
    }

    return 0;
}

You can see that main calls the function server and passes the IOService, portNumber and logger. The logger is passed by reference, thusly:

using boost::asio::ip::tcp;

void server(boost::asio::io_service &ioService, unsigned int port, Log &logger)
{
    logger.commitStatus("Server Start");

    tcp::acceptor acc(ioService, tcp::endpoint(tcp::v4(), port));

    while(true)
    {
        tcp::socket sock(ioService);
        acc.accept(sock);

        std::thread newThread(session, &sock, logger);
        newThread.detach();
    }

    logger.commitStatus("Server closed");
}

I get a compiler error when I try to pass the logger (or the socket) to the thread by reference, but I do not get the error when passing it to the session() by reference

static void session(tcp::socket *sock, Log &logger)
{
    std::cout << " session () " << std::endl;
}

Now I thought that I understood correctly that a reference is the same as passing a pointer. That is, it does not call the copy constructor, it simply passes the pointer, which it lets you syntactically treat like it's not a pointer.

error C2248: 'Log::Log' : cannot access private member declared in class 'Log'

1> \log.h(55) : see declaration of 'Log::Log'

1> \log.h(28) : see declaration of 'Log'

...

: see reference to function template instantiation 'std::thread::thread(_Fn,_V0_t &&,_V1_t)' being compiled

1> with

1> [

1> Fn=void (_cdecl *)(boost::asio::ip::tcp::socket *,Log &),

1> _V0_t=boost::asio::ip::tcp::socket *,

1> _V1_t=Log &

1> ]

However if I modify it to pass a pointer, everything is happy

...
        std::thread newThread(session, &sock, &logger);
...

static void session(tcp::socket *sock, Log *logger)
{
    std::cout << " session () " << std::endl;
}

Why is passing by reference calling my copy constructor. Is there something special happening here because of std::thread? Did I misunderstand the copy constructor and pass by reference?

I get a different but equally baffling error if I try to use std::move() as it is done in the example. Is it possible my VS2012 is not implementing C++11 correctly?

xaviersjs
  • 1,579
  • 1
  • 15
  • 25
  • Where did you get information that it's passed by reference? – zoska Jan 10 '14 at 16:14
  • @zoska: He's clearly thinking of `session` taking it by reference, forgetting entirely about the intermediate call(s). – Lightness Races in Orbit Jan 10 '14 at 16:46
  • Since this question is marked C++11: You could/should hide the copy constructor and assignment operator using the delete keyword. – Kit Fisto Jan 10 '14 at 17:33
  • @Ligtness, yes I was completely forgetting about the intermediate calls. Stupid mistake I guess – xaviersjs Jan 11 '14 at 09:27
  • @Kit, I tried to disable the copy constructor and assignment operator using the delete keyword, and for some reason it complained. Something about, 'unexpected token before ;' I can't remember exactly what the error message was. I'm guessing VS2012 doesn't support all C++11 features yet. – xaviersjs Jan 11 '14 at 09:27
  • @user1196033: You are right. You need VS2013 for that feature (see http://msdn.microsoft.com/en-us/library/hh567368.aspx). – Kit Fisto Jan 12 '14 at 16:38
  • http://stackoverflow.com/questions/8299545/passing-arguments-to-thread-function – Ciro Santilli OurBigBook.com Jun 17 '15 at 13:22

2 Answers2

65

std::thread takes its arguments by value. You can get reference semantics back by using std::reference_wrapper:

std::thread newThread(session, &sock, std::ref(logger));

Obviously you must make sure that logger outlives the thread.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • 5
    That worked beautifully, thank you. I did not know about std::ref(). – xaviersjs Jan 13 '14 at 10:32
  • 1
    To be explicit, although the session function takes Log by reference, the std::thread's constructor takes Log by value. – Cloud Aug 19 '19 at 18:01
6

I get a compiler error when I try to pass the logger (or the socket) to the thread by reference

It is not sufficient for the thread's entrypoint function to take a reference type: the thread object itself takes its arguments by value. This is because you usually want a copy of objects in a separate thread.

To get around this, you may pass std::ref(logger), which is a reference wrapper hiding reference semantics under a copyable object.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055