2017-05-04 138 views
13

std::bindstd::thread分享一些設計原則。由於兩者的存儲與傳遞的參數本地OBJETS,我們需要爲使用std::refstd::cref如果引用語義需要:By-ref參數:這是std :: thread和std :: bind之間的不一致嗎?

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! 

乍一看我以爲這是一個編譯器錯誤,但錯誤確實是合法的。看起來模板版本std::thread的構造函數由於30.3.1.2強加的copy decaying要求(轉換int&int)無法正確推導出參數。

問題是:爲什麼不需要類似std::bind的論點?或者這是顯而易見的意圖不一致?

注意:解釋爲什麼它不是重複在下面的評論。

+2

這不是重複的。 *這個問題正在解釋*我*問題的前提。此外,它不會觸及std :: thread。實際上,我所指的是其中一個註釋的一個反例(關於std :: thread的構造函數 –

回答

8

bind返回的函數對象是爲重用而設計的(即調用被多次調用)。因此它必須將其綁定的參數作爲左值傳遞,因爲您不想從所述參數移動或稍後調用會看到移動綁定參數。 (同樣,您希望函數對象也被稱爲左值)。

這個問題不適用於std::thread和朋友。線程函數只會被提供參數調用一次。從他們身上移開是完全安全的,因爲沒有別的東西會看着他們。它們實際上是臨時副本,僅用於新線程。因此函數對象被稱爲右值,參數作爲右值傳遞。

+2

^^由於第一段的原因,你可以使用'std :: bind'的結果作爲有狀態的函數對象,因爲它有非靜態的數據成員,可以傳遞給目標對象並進行修改,在通話之間保持狀態可變lambda表達式對於由副本捕獲的變量具有類似的屬性。這是設計而不是'std :: bind'中的缺陷。 –

6

std::bind由於lambda的存在而到達時大多已經過時。隨着C++ 14的改進和C++ 17 std::applybind的其餘用例幾乎沒有了。

即使在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,而123x遞增副本存儲內f

使用lambda時,可變的存儲狀態和外部參考之間的差異可以變得清晰。

auto b = [&x]{ f(x); }; 

VS

auto b = [x]()mutable{ f(x); }; 

其中之一的副本x然後調用f反覆就可以了,其它的引用傳遞到xf

確實沒有辦法用bind來做到這一點,而不允許f作爲參考訪問存儲的副本x

對於std::thread,如果你想要這種可變的本地複製行爲,你只需使用lambda。

std::thread t1([x]()mutable{ f(x); }); 

事實上,我認爲大多數C++ 11的INVOKE語法似乎是沒有C++ 14個lambda表達式功率,並在語言std::apply的遺產。 lambda和std::apply沒有解決的情況很少(需要申請,因爲lambda不容易支持移動包裝,然後將它們放入其中)。

但是我們沒有時間機器,所以我們有這些多種並行的方式來表達在C++的特定上下文中調用某些東西的想法。

+1

std :: bind不是(直到今天)已經過時了),這就是爲什麼它已經在C++ 11中引入與lambdas一起,實際上已經被引入lambdas的過時了,像std :: mem_fn或std :: bind1st(std :: bind2nd)。事實上,std :: bind只是* C++中的部分函數應用的機制(https://en.wikipedia.org/wiki/Partial_application)。 –

+1

@LeandroT。C.Melo'[x](auto && ... args) - > decltype(auto){return f(x,decltype(args)(args)...); }'我只是部分地將'x'應用於'f'而不使用'bind'。更復雜的案例可以用在更一般的案例中。像綁定一組參數。 – Yakk

+1

有趣的解決方案。我認爲這不會起作用(開箱即用)成員函數雖然...?無論如何,我應該說唯一的*庫組件*。從某種意義上說,如果我們從一個「脫糖」的角度思考,我們可以考慮一堆「過時」的東西。 –

6

從我所知道的,thread開始時基本上與bind相同的規則,但在2010年修改了N3090以承擔您確定的約束。

用它來平分各種貢獻,我相信你正在尋找LWG issue 929。具有諷刺意味的是,其意圖似乎已經使thread構造函數的約束更少了。當然,沒有提及bind,雖然this wording was later also applied to async(LWG 1315之後的「清理」部分),所以我會說bind被拋在後面。

雖然這很難確定,所以我會建議詢問the committee itself