在std :: bind和std :: thread中移动语义/行为

时间:2015-04-13 02:25:59

标签: c++ c++11 gcc move-semantics stdthread

请看下面的简单测试程序,你可以只复制和测试。我尝试使用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)我很好奇为什么在functort2传入之前有 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;
}

感谢。

1 个答案:

答案 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()需要运作。