std::thread 使用 class 参数初始化导致 class 对象被多次复制
std::thread initialization with class argument results with class object being copied multiple times
好像如果你创建一个class的对象,并传递给std::thread初始化构造函数,那么class对象的构造和销毁就多达4个总次数。我的问题是:你能一步步解释这个程序的输出吗?为什么class在这个过程中被构造、复制构造和销毁这么多次?
示例程序:
#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;
}
输出为:
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
使用 gcc 4.9.2 编译,未优化。
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
// ...
}
有很多 copying/moving 在后台进行。但是注意,调用线程构造函数时,既没有调用复制构造函数也没有调用移动构造函数。
考虑这样一个函数:
template<typename T> void foo(T&& arg);
当你有对模板参数的右值引用时,C++ 对此有一些特殊处理。我将在这里概述规则。当您使用参数调用 foo
时,参数类型将为
- && - 当参数是右值时
- & - 所有其他情况
也就是说,参数将作为右值引用或标准引用传递。无论哪种方式,都不会调用构造函数。
现在看线程对象的构造函数:
template <class Fn, class... Args>
explicit thread (Fn&& fn, Args&&... args);
此构造函数应用相同的语法,因此参数永远不会 copied/moved 到构造函数参数中。
下面的代码包含一个示例。
#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;
}
这段代码的输出是
Default constructor, iCount = 1
About to create thread object
Copy constructor, id = 2
Move constructor, id = 2
Move constructor, id = 2
doNothing
- 首先,创建对象。
- 我们调用我们的测试函数只是为了看看没有任何反应,没有构造函数调用。
- 因为我们将左值传递给线程构造函数,参数具有类型左值引用,因此对象被复制(使用复制构造函数)到线程对象中。
- 对象被移入底层线程(由线程对象管理)
- 对象最终被移入线程函数 doNothing 的参数中
好像如果你创建一个class的对象,并传递给std::thread初始化构造函数,那么class对象的构造和销毁就多达4个总次数。我的问题是:你能一步步解释这个程序的输出吗?为什么class在这个过程中被构造、复制构造和销毁这么多次?
示例程序:
#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;
}
输出为:
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
使用 gcc 4.9.2 编译,未优化。
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
// ...
}
有很多 copying/moving 在后台进行。但是注意,调用线程构造函数时,既没有调用复制构造函数也没有调用移动构造函数。
考虑这样一个函数:
template<typename T> void foo(T&& arg);
当你有对模板参数的右值引用时,C++ 对此有一些特殊处理。我将在这里概述规则。当您使用参数调用 foo
时,参数类型将为
- && - 当参数是右值时
- & - 所有其他情况
也就是说,参数将作为右值引用或标准引用传递。无论哪种方式,都不会调用构造函数。
现在看线程对象的构造函数:
template <class Fn, class... Args>
explicit thread (Fn&& fn, Args&&... args);
此构造函数应用相同的语法,因此参数永远不会 copied/moved 到构造函数参数中。
下面的代码包含一个示例。
#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;
}
这段代码的输出是
Default constructor, iCount = 1
About to create thread object
Copy constructor, id = 2
Move constructor, id = 2
Move constructor, id = 2
doNothing
- 首先,创建对象。
- 我们调用我们的测试函数只是为了看看没有任何反应,没有构造函数调用。
- 因为我们将左值传递给线程构造函数,参数具有类型左值引用,因此对象被复制(使用复制构造函数)到线程对象中。
- 对象被移入底层线程(由线程对象管理)
- 对象最终被移入线程函数 doNothing 的参数中