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

时间:2018-12-17 22:23:08

标签: c++ multithreading methods copy-constructor ref

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

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

Conan is attacking for 5!

我有4个疑问:

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

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

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

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

    在所有3种情况下?

  3. 如果我们排除第一种情况(由于效率低下),我应该在&conanstd::ref(conan)之间使用什么?

  4. 这与std::bind所需的语法有某种联系吗?

1 个答案:

答案 0 :(得分:4)

  

为什么我有该对象的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上指向对象的成员函数的指针的调用。

  

如果我们排除第一种情况(由于效率低下),我应该在&conanstd::ref(conan)之间使用什么?

这主要是基于意见的。尽管第一个版本的编写时间较短,但它们基本上都在做相同的事情。

  

这与std::bind所需的语法有某种联系吗?

种类。 std::bind的{​​{1}}也使用operator()来实现,因此它们具有非常通用的界面。


所有这些都说明您可以使用lambda来给自己一个通用的界面。

std::invoke

可以改写为

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

您几乎可以使用此表单来调用任何其他函数。我发现看到lambda时更容易解析。