简体   繁体   English

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

[英]C++ method in thread. Difference between passing: object, object's address, std::ref of object

I am trying to execute an object's method in a C++ thread. 我正在尝试在C ++线程中执行对象的方法。

I am able to do it, by passing the method's address and the object (or the object's address, or std::ref(my_obj)) to the thread's constructor. 通过将方法的地址和对象(或对象的地址,或std :: ref(my_obj))传递给线程的构造函数,我能够做到这一点。

I observed that if I pass the object, rather than the object's address or std::ref(my_obj), then the object gets copied twice (I'm printing some info in the copy constructor to see that). 我观察到,如果传递对象而不是对象的地址或std :: ref(my_obj),则该对象将被复制两次 (我在复制构造函数中打印一些信息以查看该信息)。

Here is the code: 这是代码:

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

}

The output I get in this case is 我在这种情况下得到的输出是

Copying warrior: "Conan" into : "Copied Conan"
Copying warrior: "Copied Conan" into : "Copied Copied Conan"
Copied Copied Conan is attacking for 5!

While if I simply pass &conan or std::ref(conan) as a second argument to thread t(...) (instead of passing conan ), the output is just: 虽然如果我只是简单地将&conanstd::ref(conan)作为thread t(...)的第二个参数传递(而不是传递conan ),则输出仅为:

Conan is attacking for 5!

I have 4 doubts: 我有4个疑问:

  1. Why is that I have 2 copies of the object instead of 1? 为什么我有该对象的2个副本而不是1个?

    I was expecting that by passing the instance of the object to the thread's constructor, the object would get copied once in the thread's own stack, and then the attack() method would be called on that copy. 我期望通过将对象的实例传递给线程的构造函数,该对象将在线程自己的堆栈中被复制一次 ,然后在该副本上调用attack()方法。

  2. What is the exact reason why the thread's constructor can accept an object, an address, or a std::ref ? 线程的构造函数可以接受对象,地址或std::ref的确切原因是什么? Is it using this version of the constructor (which I admit I do not fully understand) 是否使用此版本的构造函数(我承认我不太了解)

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

    in all 3 cases? 在所有三种情况下?

  3. If we exclude the first case (since it's inefficient), what should I use between &conan and std::ref(conan) ? 如果排除第一种情况(由于效率低下),在&conanstd::ref(conan)之间应该使用什么?

  4. Is this somehow related to the syntax required by std::bind ? 这是否与std::bind所需的语法有关?

Why is that I have 2 copies of the object instead of 1? 为什么我有该对象的2个副本而不是1个?

When you spin up a thread the parameters are copied into the thread object. 旋转线程时,参数将复制到线程对象中。 Those parameters are then copied into the actual thread that gets created, so you have two copies. 然后将这些参数复制到创建的实际线程中,因此您有两个副本。 This is why you have to use std::ref when you want to pass parameter that the function takes by reference. 这就是为什么要传递函数通过引用获取的参数时必须使用std::ref原因。

What is the exact reason why the thread's constructor can accept an object, an address, or a std::ref? 线程的构造函数可以接受对象,地址或std :: ref的确切原因是什么? Is it using this version of the constructor (which I admit I do not fully understand) 是否使用此版本的构造函数(我承认我不太了解)

std::thread basically starts the new thread with a call like std::thread基本上通过如下调用来启动新线程

std::invoke(decay_copy(std::forward<Function>(f)), 
            decay_copy(std::forward<Args>(args))...);

std::invoke is built to handle all different sorts of callables and one of those is when it has a member function pointer and an object, and it calls the function appropriately. std::invoke用于处理所有不同种类的可调用对象,其中之一是当它具有成员函数指针和对象时,并且会适当地调用该函数。 It also knows about std::reference_wrapper and can handle calling a pointer to a member function on a std::reference_wrapper to an object. 它还了解std::reference_wrapper并且可以处理在对象的std::reference_wrapper上调用指向成员函数的指针。

If we exclude the first case (since it's inefficient), what should I use between &conan and std::ref(conan) ? 如果排除第一种情况(由于效率低下),在&conanstd::ref(conan)之间应该使用什么?

This is primarily opinion based. 这主要是基于意见的。 They both essentially do the same thing, although the first version is shorter to write. 尽管第一个版本的编写时间较短,但它们实际上都在做相同的事情。

Is this somehow related to the syntax required by std::bind ? 这是否与std::bind所需的语法有关?

Kind of. 的种类。 std::bind 's operator() is also implemented using std::invoke so they have a very common interface. std::bindoperator()也使用std::invoke因此它们具有非常通用的接口。


All of that said you can use a lambda to give yourself a common interface. 所有这些都说明您可以使用lambda来给自己一个通用的界面。

thread t(&Warrior::attack, conan, 5);

can be rewritten as 可以改写成

thread t([&](){ return conan.attack(5); });

And you can use this form for pretty much any other function you want to call. 而且,您可以将此表单用于几乎所有您要调用的其他函数。 I find it is easier to parse when seeing a lambda. 我发现看到lambda时更容易解析。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM