Почему так сложно иметь поток внутри класса в c ++ и вызывать его с помощью конструктора? - PullRequest
0 голосов
/ 26 марта 2020

Я пытался вызвать потоки из конструкторов классов, но безрезультатно. Почему так трудно передать функцию или объект в поток.

#include <iostream>
#include <thread>
class a{
        public:
        a();
        std::thread t;
        int data;
        //virtual void fn();
};

void fn(a *p)
{
        std::cout << "Thread Function : " << p->data << std::endl;
}

a::a():data(10)
//a::a():data(10),t(fn,this)
{

        void (*fnp)(a *p) = fn;
        fn(this);
        fnp(this);
        t(fnp, this);
}

int main ()
{
        a av;
        return 0;
}

Его вывод выглядит так:

preetam@preetam-GL702ZC:~/Desktop$ g++ v.cpp -lpthread
v.cpp: In constructor ‘a::a()’:
v.cpp:23:13: error: no match for call to ‘(std::thread) (void (*&)(a*), a*)’
  t(fnp, this);

Все, что я хочу, это запустить поток из конструктора и пусть этот поток легко обращается к членам класса.

1 Ответ

1 голос
/ 26 марта 2020

Есть несколько способов сделать это. Тот, который больше всего похож на код в вопросе, просто так:

a::a() : data(10) {
    std::swap(t, std::thread(fn, this));
}

Это потому, что потоки создаются в конструкторе объекта потока. В этом коде t создается по умолчанию, потому что он не упоминается в списке инициализатора. Код в теле конструктора создает временный объект потока и заменяет его созданным по умолчанию объектом t. После перестановки t имеет поток, который выполняет fn(this), а временный объект имеет созданный по умолчанию поток. В конце оператора временный объект потока уничтожается.

Вместо этого подхода создания и замены вы также можете создать поток непосредственно в списке инициализатора:

a::a() : data(10), t(fn, this) { // won't work, though...
}

Проблема здесь немного тонкая: несмотря на то, что инициализатор для data стоит перед инициализатором для t, конструктор для t запускается до инициализации data. Так что есть гонка данных. Это происходит потому, что объекты-члены создаются в порядке объявление ; t объявляется до data, поэтому t создается до инициализации data. Независимо от их порядка в списке инициализатора конструктора.

Итак, решение состоит в том, чтобы изменить порядок объявлений:

class a {
    int data;
    std::thread t;
    a();        
};

Теперь предыдущий код будет работать правильно.

Если вы пишете такой код с важной зависимостью от порядка объявления членов, вы обязаны будущим сопровождающим, включая вас самим, документировать эту зависимость с комментарием:

class a {
    // IMPORTANT: data must be declared before t
    int data;
    std::thread t;
    a();        
};

Также обратите внимание, что при обоих этих подходах запускаемый поток работает с объектом, конструктор которого еще не завершил работу. Если функция потока использует какие-либо элементы этого объекта, и конструктор изменяет эти элементы после запуска потока, вы получаете гонку данных, следовательно, неопределенное поведение.

В частности, если вы планируете добавить виртуальные функции на a и переопределить их в производных классах, вы обречены. Когда поток запускается, конструктор базового класса все еще работает, а инициализаторы членов и тело конструктора производного класса не выполняются. Они будут запускать после конструктора базового класса, и опять же, вы глубоко в неопределенном поведении.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...