web-dev-qa-db-fra.com

std :: thread passe par référence appelle le constructeur de copie

Eh bien, j'ai un problème avec le passage de données dans un thread en utilisant std :: thread. Je pensais que je comprenais la sémantique générale des constructeurs de copie, etc. mais il semble que je ne comprends pas très bien le problème. J'ai une classe simple appelée Log qui a caché son constructeur de copie ainsi:

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 &);
}

maintenant j'ai un principal basé fortement sur 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;
}

Vous pouvez voir que main appelle le serveur de fonctions et transmet IOService, portNumber et logger. L'enregistreur est transmis par référence, ainsi:

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");
}

J'obtiens une erreur du compilateur lorsque j'essaie de transmettre l'enregistreur (ou le socket) au thread par référence, mais je n'obtiens pas l'erreur lorsque je le passe à la session () par référence

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

Maintenant, je pensais avoir bien compris qu'une référence équivaut à passer un pointeur. Autrement dit, il n'appelle pas le constructeur de copie, il passe simplement le pointeur, qu'il vous permet de traiter syntaxiquement comme s'il ne s'agissait pas d'un pointeur.

erreur C2248: 'Log :: Log': impossible d'accéder au membre privé déclaré dans la classe 'Log'

1>\log.h (55): voir la déclaration de 'Log :: Log'

1>\log.h (28): voir la déclaration de 'Log'

...

: voir référence à l'instanciation du modèle de fonction 'std :: thread :: thread (_Fn, _V0_t &&, _ V1_t)' en cours de compilation

1> avec

1> [

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

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

1> _V1_t = Journal &

1>]

Cependant si je le modifie pour passer un pointeur, tout est content

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

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

Pourquoi passe par référence en appelant mon constructeur de copie . Y a-t-il quelque chose de spécial qui se passe ici à cause de std :: thread? Ai-je mal compris le constructeur de copie et passé par référence?

J'obtiens une erreur différente mais tout aussi déroutante si j'essaie d'utiliser std :: move () comme cela se fait dans l'exemple. Est-il possible que mon VS2012 n'implémente pas correctement C++ 11?

29
xaviersjs

std::thread prend ses arguments par valeur. Vous pouvez récupérer la sémantique de référence en utilisant std::reference_wrapper:

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

Évidemment, vous devez vous assurer que logger survit au thread.

58
juanchopanza

J'obtiens une erreur du compilateur lorsque j'essaie de transmettre l'enregistreur (ou le socket) au thread par référence

Il ne suffit pas que la fonction point d'entrée du thread prenne un type de référence: l'objet thread lui-même prend ses arguments par valeur. C'est parce que vous voulez généralement une copie des objets dans un thread séparé.

Pour contourner ce problème, vous pouvez passer std::ref(logger), qui est un wrapper de référence masquant la sémantique de référence sous un objet copiable.

4