在 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) 我很好奇为什么在functort2 都传入了线程?

(2) 为什么在调用 f() 之前有 t2 MOVEt1 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() 需要工作。