线程中的 C++ 方法。传递之间的区别:对象、对象的地址、对象的 std::ref

Mic*_*ini 4 c++ methods multithreading ref copy-constructor

我正在尝试在 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

}
Run Code Online (Sandbox Code Playgroud)

我在这种情况下得到的输出是

Copying warrior: "Conan" into : "Copied Conan"
Copying warrior: "Copied Conan" into : "Copied Copied Conan"
Copied Copied Conan is attacking for 5!
Run Code Online (Sandbox Code Playgroud)

如果我只是将&conanstd::ref(conan)作为第二个参数thread t(...)传递给(而不是传递conan),则输出只是:

Conan is attacking for 5!
Run Code Online (Sandbox Code Playgroud)

我有4个疑问:

  1. 为什么我有对象的 2 个副本而不是 1 个?

    我期望通过将对象的实例传递给线程的构造函数,对象将在线程自己的堆栈中复制一次,然后attack()在该副本上调用该方法。

  2. 线程的构造函数可以接受对象、地址或 的确切原因是std::ref什么?是否使用了这个版本的构造函数(我承认我不完全理解)

    template< class Function, class... Args > explicit thread( Function&& f, Args&&... args );

    在所有 3 种情况下?

  3. 如果我们排除第一种情况(因为它效率低下),我应该在&conan和之间使用什么std::ref(conan)

  4. 这是否与std::bind?所需的语法有关?

Nat*_*ica 7

为什么我有对象的 2 个副本而不是 1 个?

当您启动一个线程时,参数被复制到线程对象中。然后将这些参数复制到创建的实际线程中,因此您有两个副本。这就是为什么std::ref当您想传递函数通过引用接受的参数时必须使用的原因。

线程的构造函数可以接受对象、地址或 std::ref 的确切原因是什么?是否使用了这个版本的构造函数(我承认我不完全理解)

std::thread 基本上用这样的调用启动新线程

std::invoke(decay_copy(std::forward<Function>(f)), 
            decay_copy(std::forward<Args>(args))...);
Run Code Online (Sandbox Code Playgroud)

std::invoke构建用于处理所有不同类型的可调用对象,其中之一是当它具有成员函数指针和对象时,它会适当地调用该函数。它还知道std::reference_wrapper并可以处理调用指向std::reference_wrapper对象上的成员函数的指针。

如果我们排除第一种情况(因为它效率低下),我应该在&conan和之间使用什么std::ref(conan)

这主要是基于意见的。他们基本上都做同样的事情,尽管第一个版本写起来更短。

这是否与std::bind?所需的语法有关?

的种类。 std::bind'soperator()也是使用实现的,std::invoke所以它们有一个非常通用的接口。


综上所述,您可以使用 lambda 来为自己提供一个通用接口。

thread t(&Warrior::attack, conan, 5);
Run Code Online (Sandbox Code Playgroud)

可以改写为

thread t([&](){ return conan.attack(5); });
Run Code Online (Sandbox Code Playgroud)

并且您几乎可以将这种形式用于您想要调用的任何其他函数。我发现看到 lambda 时更容易解析。