请看下面的简单测试程序,你可以只复制和测试。我尝试使用gcc 4.9
编译好。
#include <iostream>
#include <functional>
#include <thread>
#include <string>
class Test
{
public:
Test(const Test &t) { this->name = t.name; std::cout << name << ": copy constructor" << std::endl; }
Test(Test &&t) {this->name = std::move(t.name); std::cout << name << ": move contructor" << std::endl; }
Test(const std::string &name) {this->name=name;}
Test &operator=(const Test &t) { this->name = t.name; std::cout << name << ": copy operator = " << std::endl; return *this; }
Test &operator=(Test &&t) { this->name = std::move(t.name); std::cout << name << ": move operator = " << std::endl; return *this; }
std::string name;
};
class A
{
public:
void f(Test t1, Test t2)
{
std::cout << "running f" << std::endl;
}
void run()
{
std::cout << "running run" << std::endl;
Test t1("t1");
Test t2("t2");
auto functor = std::bind(&A::f, this, t1, std::placeholders::_1);
std::cout << "functor created by bind, t1 is passed into functor" << std::endl;
std::thread t(functor, t2);
std::cout << "thread created, functor and t2 passed into thread" << std::endl;
t.join();
}
};
int main()
{
A a;
a.run();
return 0;
}
程序为gcc 4.9(mingw)
提供以下输出跑步
t1:复制构造函数
由bind创建的仿函数,t1被传递给仿函数
t2:复制构造函数
t1:复制构造函数
t2:移动构造函数
t1:移动构造函数
线程创建,仿函数和t2传递到线程
t2:移动构造函数
t1:复制构造函数
运行f
请注意大胆。
(1)我很好奇为什么在functor
和t2
传入之前有 t2移动和 t1移动线程?
(2)为什么在致电f()
之前有 t2 MOVE 和 t1 COPY ?
gcc的库实现是否会进行一些优化以将COPY转换为MOVE以提高效率?例如,在致电f
之前,t2
已移入f(Test t1, Test t2)
?
如果我将上述两行更改为,
auto functor = std::bind(&A::f, this, std::move(t1), std::placeholders::_1);
std::thread t(std::move(functor), std::move(t2));
然后一切都变得移动,除了最后一次“t1复制”。
(3)为什么t1
仍然被复制?这与(2)有关。
如果我再换一行,
void f(Test &t1, Test &t2)
然后无法编译。
(4)不是std::bind
&amp; std::thread
的内部实施商店对象t1
&amp; t2
哪个是左值?为何选择Test&amp;将失败?我很好奇标准说的是什么。
如果我将其更改为,
void f(const Test &t1, const Test &t2)
一切正常,最后两个t2
移动和t1
副本被删除。
(5)我只是希望有人向我确认这是否有效并且没有悬挂引用的危险,即使我们将线程t
存储在其他地方也是如此。例如,以下是否仍然有效?
class A
{
public:
void f(const Test &t1, const Test &t2)
{
std::cout << "running f" << std::endl;
}
void run()
{
std::cout << "running run" << std::endl;
Test t1("t1");
Test t2("t2");
auto functor = std::bind(&A::f, this, std::move(t1), std::placeholders::_1);
std::cout << "functor created by bind, t1 is passed into functor" << std::endl;
std::thread t(std::move(functor), std::move(t2));
std::cout << "thread created, functor and t2 passed into thread" << std::endl;
t_internal.swap(t);
}
std::thread t_internal;
};
int main()
{
A a;
a.run();
a.t_internal.join();
return 0;
}
感谢。
答案 0 :(得分:3)
(1)我很好奇为什么在仿函数和t2传递到线程之前有t2移动和t1移动?
这是一个内部实现细节。会发生什么是thread
的构造函数将首先绑定仿函数并使用类似于std::bind
的内部绑定器提供参数,然后将生成的绑定仿函数移动到分配用于存储它的内存中。
(2)为什么在调用f()之前有t2 MOVE和t1 COPY?
std::thread
执行INVOKE(DECAY_COPY(std::forward<F>(f)), DECAY_COPY(std::forward<Args>(args))...)
。 DECAY_COPY
始终返回一个右值,因此std::thread
将所有值都作为右值传递。
std::bind
将绑定参数作为左值传递,并将完全转发的内容传递给其operator()
。最终结果是f
的第一个参数是从左值(因此是副本)构造的,而第二个参数是从右值构造的(因此移动)。
(3)为什么t1仍然是副本?这与(2)有关。
std::bind
将绑定参数作为左值传递。
(4)不是绑定&amp;线程的内部实现存储对象t1&amp; t2哪个是左值?为何选择Test&amp;将失败?我很好奇标准说的是什么。
第二个参数作为右值传递,并且不会绑定到Test &
。
(5)我只是希望有人向我确认这是否有效并且没有悬挂引用的危险,即使我们将线程存储在其他地方也是如此。例如,以下是否仍然有效?
没关系。销毁std::thread
对象不会破坏线程的参数。它们将一直存在直到线程终止。毕竟,detach()
需要运作。