在 std::bind 和 std::thread 中移动 semantics/behaviors
move semantics/behaviors in std::bind and std::thread
请看下面的简单测试程序,复制测试即可。我试过 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) 提供以下输出
running run
t1: copy constructor
functor created by bind, t1 is passed into functor
t2: copy constructor
t1: copy constructor
t2: move contructor
t1: move contructor
thread created, functor and t2 passed into thread
t2: move contructor
t1: copy constructor
running f
请注意粗体。
(1) 我很好奇为什么在functor
和t2
都传入了线程?
(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 copy"。
(3) 为什么t1
还是被复制了?这与(2)有关。
如果我再换一行,
void f(Test &t1, Test &t2)
然后编译失败
(4) std::bind
& std::thread
的内部实现存储对象 t1
& t2
不是左值吗?为什么调用 Test & 会失败?我很好奇标准是怎么说的。
如果我把它改成,
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;
}
谢谢。
(1) I am curious why there is a t2 move and a t1 move before the functor and the t2 are passed into the thread?
这是一个内部实现细节。 thread
的构造函数将首先使用类似于 std::bind
的内部绑定器绑定仿函数并提供参数,然后将生成的绑定仿函数移动到分配的内存中以存储它。
(2) And why there is a t2 MOVE and t1 COPY before calling f()?
std::thread
执行 INVOKE(DECAY_COPY(std::forward<F>(f)), DECAY_COPY(std::forward<Args>(args))...)
。 DECAY_COPY
始终 return 一个右值,因此 std::thread
将所有内容作为右值传递。
同时,std::bind
将绑定参数作为左值传递,并完美转发传递给其 operator()
的内容。最终结果是 f
的第一个参数是从左值构造的(因此是复制),而第二个参数是从右值构造的(因此是移动)。
(3) Why t1 is still copy? This is related to (2).
std::bind
将绑定参数作为左值传递。
(4) isn't bind & thread's internal implemenatation store object t1 & t2 which are lvalue? Why call Test & will fail? I am curious what standard says.
第二个参数作为右值传递,不绑定到 Test &
。
(5) I just want someone to confirm with me whether this is valid and there's no danger of dangling reference, even IF we store the thread t somewhere else. For example, is the following still valid?
没关系。销毁 std::thread
对象不会销毁线程的参数。它们将一直存在,直到线程终止。毕竟,detach()
需要工作。
请看下面的简单测试程序,复制测试即可。我试过 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) 提供以下输出
running run
t1: copy constructor
functor created by bind, t1 is passed into functor
t2: copy constructor
t1: copy constructor
t2: move contructor
t1: move contructor
thread created, functor and t2 passed into thread
t2: move contructor
t1: copy constructor
running f
请注意粗体。
(1) 我很好奇为什么在functor
和t2
都传入了线程?
(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 copy"。
(3) 为什么t1
还是被复制了?这与(2)有关。
如果我再换一行,
void f(Test &t1, Test &t2)
然后编译失败
(4) std::bind
& std::thread
的内部实现存储对象 t1
& t2
不是左值吗?为什么调用 Test & 会失败?我很好奇标准是怎么说的。
如果我把它改成,
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;
}
谢谢。
(1) I am curious why there is a t2 move and a t1 move before the functor and the t2 are passed into the thread?
这是一个内部实现细节。 thread
的构造函数将首先使用类似于 std::bind
的内部绑定器绑定仿函数并提供参数,然后将生成的绑定仿函数移动到分配的内存中以存储它。
(2) And why there is a t2 MOVE and t1 COPY before calling f()?
std::thread
执行 INVOKE(DECAY_COPY(std::forward<F>(f)), DECAY_COPY(std::forward<Args>(args))...)
。 DECAY_COPY
始终 return 一个右值,因此 std::thread
将所有内容作为右值传递。
std::bind
将绑定参数作为左值传递,并完美转发传递给其 operator()
的内容。最终结果是 f
的第一个参数是从左值构造的(因此是复制),而第二个参数是从右值构造的(因此是移动)。
(3) Why t1 is still copy? This is related to (2).
std::bind
将绑定参数作为左值传递。
(4) isn't bind & thread's internal implemenatation store object t1 & t2 which are lvalue? Why call Test & will fail? I am curious what standard says.
第二个参数作为右值传递,不绑定到 Test &
。
(5) I just want someone to confirm with me whether this is valid and there's no danger of dangling reference, even IF we store the thread t somewhere else. For example, is the following still valid?
没关系。销毁 std::thread
对象不会销毁线程的参数。它们将一直存在,直到线程终止。毕竟,detach()
需要工作。