С++ 11 std::thread дает ошибку: нет соответствующей функции для вызова std::thread::thread

Я тестирую потоки С++ 11 с этим кодом, но при создании потока у меня возникает ошибка нет соответствующей функции для вызова 'std::thread::thread()'.

Это как если бы что-то было не так с функцией, которую я передаю std::thread ctr, но я не понимаю, почему это неправильно. Он неполный, но мне кажется правильным:

Заголовок:

#ifndef CONNECTION_H
#define CONNECTION_H

#include <thread>
#include <mysql++.h>

class Connection
{
public:
    Connection(std::string mysqlUser, std::string mysqlPassword);
    ~Connection();

private:
    std::string mysqlUser;
    std::string mysqlPassword;
    std::string mysqlIP;
    int mysqlPort;

    mysqlpp::Connection mysqlConnection;
    std::thread connectionThread;

    void threadLoop();
};

#endif // CONNECTION_H

Источник:

#include "connection.h"

Connection::Connection(std::string mysqlUser, std::string mysqlPassword)
{
    this->mysqlUser     = mysqlUser;
    this->mysqlPassword = mysqlPassword;
    this->mysqlIP       = "localhost";    //default
    this->mysqlPort     = 3306;           //default

    //Launch thread
    std::thread connectionThread(threadLoop);

}

Connection::~Connection(){
    mysqlConnection.disconnect();
}

void Connection::threadLoop(){
    //Connect to mySQL database
    mysqlConnection = new mysqlpp::Connection(false);

    if(mysqlConnection.connect(NULL, mysqlIP.c_str(), mysqlUser.c_str(), mysqlPassword.c_str(), mysqlPort)){
        std::string consulta = "SELECT * FROM 'Coordinates'";
        mysqlpp::Query query = mysqlConnection.query(consulta);
        mysqlpp::StoreQueryResult res = query.store();
        query.reset();

    }

    while(true){
        // Stuff
    }
}

person Roman Rdgz    schedule 27.09.2012    source источник
comment
Пожалуйста, предоставьте полный, минимальный образец программы. Судя по вашему описанию, он должен уместиться в 5-10 строк или около того. См. SSCCE.ORG.   -  person Robᵩ    schedule 27.09.2012
comment
Разве конструктор std::thread не ожидает бесплатную функцию или, по крайней мере, статическую функцию-член? Как он может знать, на каком объекте вызывать Connection::threadLoop?   -  person Nicola Musatti    schedule 27.09.2012
comment
Какой компилятор вы используете?   -  person Nicola Musatti    schedule 27.09.2012
comment
@NicolaMusatti: я думаю, вы должны превратить это в ответ.   -  person Gorpik    schedule 27.09.2012
comment
@NicolaMusatti Я не знаю, я следил за учебником здесь, и он делает это так же просто, как и я solarianprogrammer.com/2011/12/16/cpp-11-thread-tutorial   -  person Roman Rdgz    schedule 27.09.2012
comment
Вы объявляете новый объект потока в своем конструкторе. Я сомневаюсь, что это то, что вы имеете в виду.   -  person juanchopanza    schedule 27.09.2012
comment
@juanchopanza Почему бы и нет? Я имею в виду, что в этом плохого?   -  person Roman Rdgz    schedule 27.09.2012
comment
Это кажется несовместимым с наличием потока с тем же именем, что и член данных.   -  person juanchopanza    schedule 27.09.2012
comment
@RomanRdgz: ваш std::thread будет уничтожен, когда конструктор Connection завершит работу. То есть он будет уничтожен немедленно. Уничтожение std::thread без присоединения к нему вызывает terminate(), что, вероятно, не то, что вам нужно.   -  person Gorpik    schedule 27.09.2012
comment
@Gorpik Ну, функция еще не закончена, я собирался использовать соединение. В любом случае спасибо за ваш совет. Куда бы вы тогда поместили std::thread?   -  person Roman Rdgz    schedule 27.09.2012
comment
@Gorpik Я использую поток, чтобы делать грязную работу, просто чтобы освободить свой графический интерфейс от обязанностей. Если я вызову соединение после создания потока, не заблокируется ли он там?   -  person Roman Rdgz    schedule 28.09.2012
comment
Я напишу ответ, объясняющий весь механизм, потому что он немного длинный для комментария.   -  person Gorpik    schedule 28.09.2012


Ответы (3)


Проблема в том, что threadLoop является функцией-членом, но нет объекта, к которому ее можно было бы применить. Просто угадал:

std::thread connectionThread(&Connection::threadLoop, this);

Но это только синтаксическая проблема; есть и логическая проблема: эта строка создает локальный объект типа std::thread, который исчезает, когда функция возвращается. Его деструктор вызовет std::terminate(), потому что поток не присоединен. Скорее всего, это должно было привязать нить к connectionThread участнику. Для этого:

std::thread thr(threadLoop, this);
std::swap(thr, connectionThread);
person Pete Becker    schedule 27.09.2012
comment
На самом деле деструктор не выбрасывает, а вызывает terminate() напрямую, в соответствии с §30.3.1.3 стандарта. В любом случае ничего хорошего. И это также произойдет, если в конечном итоге не будет вызвана connectionThread.join(). - person Gorpik; 27.09.2012
comment
Это не работает, и я не понимаю, почему я должен передавать ссылку на класс Connection с этим: ссылка говорит, что я должен передать функцию и необязательные аргументы. Пожалуйста, объясните, если я что-то упустил - person Roman Rdgz; 28.09.2012
comment
@RomanRdgz - почему это не работает? Функция-член может быть вызвана только с объектом. Когда вы передаете указатель на функцию-член в конструктор thread, вы также должны передать ему объект. Это может быть сам объект, ссылка на объект, указатель на объект (например, this) или что-то, что можно разыменовать, чтобы получить указатель или ссылку на объект (обычно интеллектуальный указатель). - person Pete Becker; 28.09.2012
comment
Спасибо @Dodgie за исправление добавления &Connection:: к конструкции connectionThread. Не знаю, почему предложенное изменение было отклонено. - person Pete Becker; 17.05.2013

В вашем коде две проблемы:

  1. Вы предоставляете неполную информацию конструктору std::thread
  2. Вы уничтожаете std::thread до того, как он присоединится к основному потоку.

Для первой проблемы, как Пит Беккер предполагает, вам необходимо предоставить объект, для которого будет вызываться функция, потому что у конструктора для std::thread нет другого способа узнать его. Предполагая, что вы хотите вызвать функцию threadLoop() для объекта Connection, который вы создаете, вы можете сделать это:

//Launch thread
std::thread connectionThread(threadLoop, this);

Внутри конструктор вызовет this->threadLoop() (где this — это полученный параметр Connection*, а не сам std::thread, конечно). И вы будете в порядке.

Вторая проблема заключается в том, что ваш std::thread уничтожается сразу после запуска, не присоединив его к основному потоку: это вызовет terminate(), что не есть хорошо. И снова Пит предлагает хорошую альтернативу. Замените приведенный выше код следующим:

// Launch thread
std::thread thr(threadLoop, this);
std::swap(thr, connectionThread);

Ситуация перед этим кодом следующая:

  • У вас есть тривиальный объект std::thread, connectionThread, который на самом деле не представляет поток

После выполнения первой строки кода:

  • У вас еще есть connectionThread
  • У вас также есть активный поток, представленный объектом std::thread thr, который будет уничтожен в конце конструктора Connection, вызывая вызов terminate(), поскольку он никогда не соединяется с основным потоком.

К счастью, на помощь приходит вторая строка кода. После его выполнения:

  • У вас есть тривиальные std::thread, thr, которые можно безопасно уничтожить, поскольку они не представляют реальный поток (поэтому он не может быть присоединен)
  • У вас есть живой поток, представленный connectionThread, объект, который не будет уничтожен, пока существует объект Connection.

Теперь проблема в том, что вы хотите присоединить connectionThread к основному потоку до того, как он будет уничтожен, но вы также хотите избежать блокировки основного потока. Подходящим моментом для этого соединения является самое позднее возможное время: когда connectionThread вот-вот будет уничтожен. И это происходит в деструкторе Connection. Итак, мы добавим строку в этот деструктор следующим образом:

Connection::~Connection(){
  mysqlConnection.disconnect();
  connectionThread.join(); // Now connectionThread can be safely destroyed
}

Кроме того, это самое безопасное место для вызова join(), потому что это гарантирует, что вы никогда не уничтожите несвязанный connectionThread. Это RAII в действии; если вы не знакомы с концепцией RAII (или RIIA, как ее иногда называют), вы можете найти много информации об этой очень важной концепции в Интернете, включая этот сайт.

Все это в совокупности: создание объекта Connection создаст новый поток; в этом потоке будет установлено новое соединение с базой данных и будет выполнен запрос, в то время как основной поток останется свободным для любого другого использования (например, для управления графическим интерфейсом). Когда объект Connection будет окончательно уничтожен, основной поток будет ждать завершения дополнительного потока (если необходимо), а затем продолжится обычное выполнение. Надеюсь, это то, чего вы хотели добиться своим кодом.

person Gorpik    schedule 28.09.2012

Как видно из cppreference, конструктор std::thread ожидает некоторую форму функции; вы можете передать ей бесплатную функцию, статическую функцию-член или одну из них, упакованную вместе со своими аргументами с помощью std::bind. Чтобы выполнить нестатическую функцию-член, вы должны использовать std::mem_fn для передайте его вместе с объектом, для которого он должен быть вызван.

person Nicola Musatti    schedule 27.09.2012
comment
Здесь нет необходимости в std::bind или std::men_fn; Конструктор std::thread знает, как обращаться с функциями-членами. Проблема в том, что нет объекта, к которому можно применить функцию-член. - person Pete Becker; 27.09.2012
comment
Я понимаю. У меня нет стандарта под рукой, и cppreference не очень ясен по этому вопросу. - person Nicola Musatti; 27.09.2012
comment
cppreference вполне понятно, но это неправильно. ‹g› Для TR1 мы изобрели терминологию INVOKE, чтобы описать, как bind и function работают с различными вызываемыми типами, включая указатели на функции-члены (второй аргумент должен быть объектом, ссылкой на объект или указателем на объект подходящего типа). std::thread::thread(Fn&&, Args&&...) использует тот же механизм. - person Pete Becker; 27.09.2012
comment
@PeteBecker: сейчас я читаю стандарт и вижу, что вы правы. А сейчас читаю ваш комментарий и вижу, что вы уже объяснили механизм :) - person Gorpik; 27.09.2012
comment
Я не понимаю, почему предоставление std::thread() бесплатной функции работает, а использование функции, объявленной в классе, - нет. В любом случае, если бы я выполнял свою функцию бесплатно, вне класса, она все равно не работала бы. - person Roman Rdgz; 28.09.2012
comment
По той же причине, по которой вы не можете вызвать его извне его класса, как в threadLoop(), но вы должны указать экземпляр Connection, для которого его нужно вызвать, как в Connection().threadLoop(). - person Nicola Musatti; 28.09.2012