简体   繁体   English

带有类参数的std :: thread初始化结果,类对象被多次复制

[英]std::thread initialization with class argument results with class object being copied multiple times

It seems that if you create an object of a class, and pass it to the std::thread initialization constructor, then the class object is constructed and destroyed as much as 4 times overall. 似乎如果你创建一个类的对象,并将它传递给std :: thread初始化构造函数,那么类对象的构造和销毁总体上是4次。 My question is: could you explain, step by step, the output of this program? 我的问题是:你能一步一步地解释这个程序的输出吗? Why is the class being constructed, copy-constructed and destructed so many times in the process? 为什么这个类在这个过程中被构造,复制和破坏很多次?

sample program: 示例程序:

#include <iostream>  
#include <cstdlib>
#include <ctime>
#include <thread>

class sampleClass {
public:
    int x = rand() % 100;
    sampleClass() {std::cout << "constructor called, x=" << x <<     std::endl;}
    sampleClass(const sampleClass &SC) {std::cout << "copy constructor called, x=" << x << std::endl;}
    ~sampleClass() {std::cout << "destructor called, x=" << x << std::endl;}
    void add_to_x() {x += rand() % 3;}
};

void sampleThread(sampleClass SC) {
    for (int i = 0; i < 1e8; ++i) { //give the thread something to do
        SC.add_to_x();
    }
    std::cout << "thread finished, x=" << SC.x << std::endl;
}

int main(int argc, char *argv[]) {
    srand (time(NULL));
    sampleClass SC;
    std::thread t1 (sampleThread, SC);
    std::cout << "thread spawned" << std::endl;
    t1.join();
    std::cout << "thread joined" << std::endl;
    return 0;
}

The output is: 输出是:

constructor called, x=92
copy constructor called, x=36
copy constructor called, x=61
destructor called, x=36
thread spawned
copy constructor called, x=62
thread finished, x=100009889
destructor called, x=100009889
destructor called, x=61
thread joined
destructor called, x=92

compiled with gcc 4.9.2, no optimization. 用gcc 4.9.2编译,没有优化。

There are a lot of copying/moving going on in the background. 在后台有很多复制/移动。 Note however, that neither the copy constructor nor the move constructor is called when the thread constructor is called. 但请注意,在调用线程构造函数时,既不调用复制构造函数也不调用移动构造函数。

Consider a function like this: 考虑这样的函数:

template<typename T> void foo(T&& arg);

When you have r-value references to template arguments C++ treats this a bit special. 当你对模板参数进行r值引用时,C ++对此有点特殊。 I will just outline the rules here. 我将在这里概述规则。 When you call foo with an argument, the argument type will be 当您使用参数调用foo时,参数类型将为

  • && - when the argument is an r-value && - 当参数是r值时
  • & - all other cases & - 所有其他情况

That is, either the argument will be passed as an r-value reference or a standard reference. 也就是说,参数将作为r值引用或标准引用传递。 Either way, no constructor will be invoked. 无论哪种方式,都不会调用构造函数。

Now look at the constructor of the thread object: 现在看一下线程对象的构造函数:

template <class Fn, class... Args>
explicit thread (Fn&& fn, Args&&... args);

This constructor applies the same syntax, so arguments will never be copied/moved into the constructor arguments. 此构造函数应用相同的语法,因此永远不会将参数复制/移动到构造函数参数中。

The below code contains an example. 以下代码包含一个示例。

#include <iostream>
#include <thread>

class Foo{
public:
    int id;

    Foo()
    {
        id = 1;
        std::cout << "Default constructor, id = " << id << std::endl;
    }

    Foo(const Foo& f)
    {
        id = f.id + 1;
        std::cout << "Copy constructor, id = " << id << std::endl;
    }

    Foo(Foo&& f)
    {
        id = f.id;
        std::cout << "Move constructor, id = " << id << std::endl;
    }
};

void doNothing(Foo f)
{
    std::cout << "doNothing\n";
}

template<typename T>
void test(T&& arg)
{
}

int main()
{
    Foo f; // Default constructor is called

    test(f); // Note here that we see no prints from copy/move constructors

    std::cout << "About to create thread object\n";
    std::thread t{doNothing, f};
    t.join();

    return 0;
}

The output from this code is 这段代码的输出是

Default constructor, iCount = 1
About to create thread object
Copy constructor, id = 2
Move constructor, id = 2
Move constructor, id = 2
doNothing
  • First, the object is created. 首先,创建对象。
  • We call our test function just to see that nothing happens, no constructor calls. 我们调用我们的测试函数只是为了看到没有任何反应,没有构造函数调用。
  • Because we pass in an l-value to the thread constructor the argument has type l-value reference, hence the object is copied (with the copy constructor) into the thread object. 因为我们将l值传递给线程构造函数,所以参数具有类型l值引用,因此将对象(使用复制构造函数)复制到线程对象中。
  • The object is moved into the underlying thread (managed by the thread object) 对象被移动到底层线程(由线程对象管理)
  • Object is finally moved into the thread-function doNothing's argument 最终将对象移入线程函数doNothing的参数中
int main(int argc, char *argv[]) {
    sampleClass SC; // default constructor
    std::thread t1 (sampleThread, SC); // Two copies inside thread constructor,
                                       //use std::ref(SC) to avoit it
    //..
}

void sampleThread(sampleClass SC) { // copy SC: pass by ref to avoid it
                                // but then modifications are for original and not the copy
  // ...
}

Fixed version Demo 修正版Demo

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

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