[英]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)提供以下輸出
跑步
t1:復制構造函數
由bind創建的仿函數,t1被傳遞給仿函數
t2:復制構造函數
t1:復制構造函數
t2:移動構造函數
t1:移動構造函數
線程創建,仿函數和t2傳入線程
t2:移動構造函數
t1:復制構造函數
跑步
請注意粗體。
(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
& std::thread
的內部實現存儲對象t1
和t2
是否是左值? 為什么打電話給測試&會失敗? 我很好奇標准說的是什么。
如果我改成它,
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)我很好奇為什么在仿函數和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
將所有內容都作為rvalue傳遞。
同時, std::bind
將綁定參數作為左值傳遞,並將完全轉發的內容傳遞給其operator()
。 最終結果是f
的第一個參數是由左值(因此是副本)構造的,而第二個參數是由右值構成的(因此是移動)。
(3)為什么t1仍然是復制? 這與(2)有關。
std::bind
將綁定參數作為lvalues傳遞。
(4)是不是綁定&thread的內部實現存儲對象t1&t2哪個是左值? 為什么打電話給測試&會失敗? 我很好奇標准說的是什么。
第二個參數作為右值傳遞,並且不會綁定到Test &
。
(5)我只是希望有人向我確認這是否有效並且沒有懸掛引用的危險,即使我們將線程存儲在其他地方也是如此。 例如,以下是否仍然有效?
沒關系。 銷毀std::thread
對象不會破壞線程的參數。 它們將一直存在直到線程終止。 畢竟, detach()
需要工作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.