Я пытаюсь выполнить метод объекта в потоке C ++.
Я могу сделать это, передавая адрес метода и объекта (или адрес объекта, или std :: ref (my_obj)) конструктору потока.
Я заметил, что если я передаю объект, а не адрес объекта или std :: ref (my_obj), то объект копируется дважды (Я печатаю некоторую информацию в конструкторе копирования, чтобы увидеть это).
Вот код:
class Warrior{
string _name;
public:
// constructor
Warrior(string name): _name(name) {}
// copy constructor (prints every time the object is copied)
Warrior(const Warrior & other): _name("Copied " + other._name){
cout << "Copying warrior: \"" << other._name;
cout << "\" into : \"" << _name << "\"" << endl;
}
void attack(int damage){
cout << _name << " is attacking for " << damage << "!" << endl;
}
};
int main(){
Warrior conan("Conan");
// run conan.attack(5) in a separate thread
thread t(&Warrior::attack, conan, 5);
t.join(); // wait for thread to finish
}
Вывод, который я получаю в этом случае
Copying warrior: "Conan" into : "Copied Conan"Copying warrior: "Copied Conan" into : "Copied Copied Conan"Copied Copied Conan is attacking for 5!
Хотя если я просто пройду &conan
или же std::ref(conan)
в качестве второго аргумента thread t(...)
(вместо прохождения conan
), вывод просто:
Conan is attacking for 5!
У меня есть 4 сомнения:
Почему у меня есть 2 копии объекта вместо 1?
Я ожидал, что, передав экземпляр объекта в конструктор потока, объект будет скопирован один раз в собственном стеке потока, а затем attack()
метод будет вызван на этой копии.
Какова точная причина, почему конструктор потока может принять объект, адрес или std::ref
? Используется ли эта версия конструктора (которую, я признаю, я не до конца понимаю)
template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );
во всех 3 случаях?
Если мы исключим первый случай (так как он неэффективен), что я должен использовать между &conan
а также std::ref(conan)
?
Это как-то связано с синтаксисом, требуемым std::bind
?
Почему у меня есть 2 копии объекта вместо 1?
Когда вы раскручиваете поток, параметры копируются в объект потока. Эти параметры затем копируются в реальный поток, который создается, поэтому у вас есть две копии. Вот почему вы должны использовать std::ref
когда вы хотите передать параметр, который функция принимает по ссылке.
Какова точная причина, почему конструктор потока может принимать объект, адрес или std :: ref? Используется ли эта версия конструктора (которую, я признаю, я не до конца понимаю)
std::thread
в основном начинается новая тема с вызовом, как
std::invoke(decay_copy(std::forward<Function>(f)),
decay_copy(std::forward<Args>(args))...);
std::invoke
Он создан для обработки всех видов вызываемых типов, и один из них — когда он имеет указатель на функцию-член и объект и соответствующим образом вызывает функцию. Это также знает о std::reference_wrapper
и может обрабатывать вызов указателя на функцию-член на std::reference_wrapper
к объекту.
Если мы исключим первый случай (так как он неэффективен), что я должен использовать между
&conan
а такжеstd::ref(conan)
?
Это в первую очередь основано на мнении. По сути, они оба делают одно и то же, хотя первую версию написать короче.
Это как-то связано с синтаксисом, требуемым
std::bind
?
Вид. std::bind
«s operator()
также реализуется с помощью std::invoke
поэтому у них очень общий интерфейс.
Все это говорит о том, что вы можете использовать лямбду, чтобы дать себе общий интерфейс.
thread t(&Warrior::attack, conan, 5);
может быть переписан как
thread t([&](){ return conan.attack(5); });
И вы можете использовать эту форму практически для любой другой функции, которую вы хотите вызвать. Я считаю, что легче разобрать, увидев лямбду.
Других решений пока нет …