[英]What is the rationale behind std::bind and std::thread always copying arguments?
[英]By-ref arguments: is this an inconsistency between std::thread and std::bind?
std::bind
和std::thread
共享一些設計原則。 由於它們都存儲了與傳遞的參數對應的本地對象,因此如果需要引用語義,我們需要使用std::ref
或std::cref
:
void f(int& i, double d) { /*...*/ }
void g() {
int x = 10;
std::bind(f, std::ref(x), _1) (3.14);
std::thread t1(f, std::ref(x), 3.14);
//...
}
但是我對最近的個人發現很感興趣: std::bind
將允許你在上面的情況下傳遞一個值,即使這不是人們通常想要的。
std::bind(f, x, _1) (3.14); // Usually wrong, but valid.
但是,對於std::thread
,情況並非如此。 以下將觸發編譯錯誤。
std::thread t2(f, x, 3.14); // Usually wrong and invalid: Error!
乍一看,我認為這是一個編譯器錯誤,但錯誤確實合法。 似乎由於30.3.1.2強加的復制衰減 要求 (轉換int&
in int
),模板化版本的std::thread
的構造函數無法正確推導出參數。
問題是:為什么不要求類似於std::bind
的參數呢? 或者這是否明顯不一致?
注意:解釋為什么它在下面的評論中不重復。
bind
返回的函數對象是為了重用而設計的(即調用被多次調用); 因此,它必須將其綁定參數作為左值傳遞,因為您不希望從所述參數移動或稍后調用將看到移動的綁定參數。 (同樣,您也希望將函數對象作為左值調用。)
這個問題不適用於std::thread
和朋友。 線程函數只能使用提供的參數調用一次。 離開它們是完全安全的,因為沒有別的東西可以看着它們。 它們實際上是臨時副本,僅用於新線程。 因此,函數對象被稱為rvalue,參數作為rvalues傳遞。
由於lambdas的存在, std::bind
在它到達時大多已經過時了。 使用C ++ 14改進和C ++ 17 std::apply
, bind
的剩余用例幾乎消失了。
即使在C ++ 11中, bind
解決了lambda在相對罕見的情況下無法解決的問題。
另一方面, std::thread
正在解決一個稍微不同的問題。 它不需要靈活的bind
來“解決每個問題”,相反它可以阻止通常是壞代碼。
在bind
情況下,傳遞給f
的引用不是x
,而是對x
的內部存儲副本的引用。 這非常令人驚訝。
void f(int& x) {
++x;
std::cout << x << '\n';
};
int main() {
int x = 0;
auto b = std::bind(f, x);
b();
b();
b();
std::cout << x << '\n';
}
版畫
1
2
3
0
其中最后的0
是原始的x
,而1
2
和3
是存儲在f
的x
的遞增副本。
使用lambda,可以明確可變存儲狀態和外部引用之間的差異。
auto b = [&x]{ f(x); };
VS
auto b = [x]()mutable{ f(x); };
其副本中的一個x
然后調用f
反復就可以了,其它的引用傳遞到x
成f
。
實際上沒有辦法用bind
執行此操作,而不允許f
訪問存儲的x
副本作為引用。
對於std::thread
,如果你想要這個可變的本地拷貝行為,你只需要使用lambda。
std::thread t1([x]()mutable{ f(x); });
事實上,我認為C ++ 11中的大部分INVOKE語法似乎都是沒有C ++ 14 power lambdas和std::apply
的遺留問題。 很少有案例沒有通過lambda和std::apply
解決(需要應用,因為lambda不能輕易支持將包移入其中然后在里面進行處理)。
但是我們沒有時間機器,所以我們有這些多種並行方式來表達在C ++中在特定上下文中調用某些東西的想法。
從我所知道的, thread
開始時基本上與bind
相同的規則,但是在2010年被N3090修改為接受你已經識別的約束。
使用它來平分各種貢獻,我相信你正在尋找LWG問題929 。
具有諷刺意味的是,其目的似乎是使thread
構造函數受到較少限制。 當然沒有提到bind
,雖然這個措辭后來也適用於async
(LWG 1315之后的“清理”部分),所以我會說bind
被拋在后面。
但是,很難確定,所以我建議問委員會本身 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.