C++ std::使用类参数进行线程初始化会导致类对象被复制多次
看起来,如果您创建一个类的对象,并将其传递给std::thread初始化构造函数,那么该类对象的构造和销毁次数总共将达到4次。我的问题是:你能一步一步地解释一下这个程序的输出吗?为什么在这个过程中要多次构造、复制和销毁类 示例程序:C++ std::使用类参数进行线程初始化会导致类对象被复制多次,c++,multithreading,class,c++11,stdthread,C++,Multithreading,Class,C++11,Stdthread,看起来,如果您创建一个类的对象,并将其传递给std::thread初始化构造函数,那么该类对象的构造和销毁次数总共将达到4次。我的问题是:你能一步一步地解释一下这个程序的输出吗?为什么在这个过程中要多次构造、复制和销毁类 示例程序: #include <iostream> #include <cstdlib> #include <ctime> #include <thread> class sampleClass { public:
#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;
}
使用GCC4.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
// ...
}
在后台有很多复制/移动操作。但是请注意,在调用线程构造函数时,不会调用复制构造函数或移动构造函数 考虑这样一个函数:
template<typename T> void foo(T&& arg);
此构造函数应用相同的语法,因此参数永远不会复制/移动到构造函数参数中
下面的代码包含一个示例
#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;
}
- 首先,创建对象
- 我们调用测试函数只是为了确保没有任何事情发生,没有构造函数调用
- 因为我们将l值传递给线程构造函数,所以参数具有l值引用类型,因此对象(使用复制构造函数)被复制到线程对象中
- 对象被移动到基础线程中(由线程对象管理)
- 对象最终移动到线程函数doNothing的参数中
-O3
,输出也是相同的。我的意思是,您将创建与线程数量相同的对象,然后将它们移动到线程中,为我的应用程序std::ref避免复制不是一个选项,因为我正在启动更多同类线程,在我这样做之前,要设置要传递的类需要做很多工作,所以我有意复制并构造它。然而,我仍然不清楚为什么std::threadt1(sampleThread,SC)
创建2个副本?@MarcinL:,请参见3)
:detacy\u copy
在返回中构造一个副本(因为在您的情况下没有移动),在调用它的地方构造另一个副本。我认为一些实现可能会将副本的数量减少到一个。那么,假设您希望生成多个线程,其中包含一个大型类的独立副本(由于其他原因需要提前创建),是否可以避免内存泄漏?如果是故意创建了两个拷贝而不是一个,那么就没有解决方案,我会错过什么吗?@MarcinL:目前,没有memleaks。您可能有一个移动构造函数,它可能比副本便宜。否则,您可以将sampleThread
更改为按常量引用,并在sampleThread
中或在创建线程之前进行复制(使用std::vector
。。@MarcinL:您似乎有一些基本的误解。为什么你认为一个对象有两个副本会泄漏内存?std::thread
在它的主体中做传递参数的内部副本。是的,这就是我说的:对象被复制(使用复制构造函数)到thread对象中。
template <class Fn, class... Args>
explicit thread (Fn&& fn, Args&&... args);
#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