2012-09-20 43 views
12

比方說,我有兩個本地智能指針,foobarC++ 11:lambda捕獲按什麼順序被破壞?

shared_ptr<Foo> foo = ... 
shared_ptr<Bar> bar = ... 

這些智能指針是圍繞資源的包裝,由於某種原因必須在訂單foo,然後bar遭到破壞。

現在我想創建一個lambda,它使用foobar,但超出了包含它們的範圍。所以我想通過值捕捉它們,就像這樣:

auto lambda = [foo, bar]() { ... }; 

這個函數對象中創建的foobar副本。當函數對象被破壞時,這些副本也會被破壞,但我關心這種情況發生的順序。所以我的問題是:

當一個lambda對象被破壞時,按什麼順序被它的值捕獲破壞?我怎麼能(希望)影響這個命令?

+4

我覺得很有趣,也考慮'[=]'。 –

+0

@ R.MartinhoFernandes:'[foo,bar]'相當於'[= foo,= bar]',即它是一個副本。 –

+0

@大衛:我認爲他的字面意思是'[=]',即考慮聲明順序是什麼,而不列出自己的變量。 (顯然這是一個有爭議的問題,因爲聲明順序未指定,不管捕獲的方式如何) – ildjarn

回答

15

該規範涵蓋了這種...。從5.1.2,第14段:

實體被複制捕獲,如果它被隱式的捕捉,默認爲=或者如果它明確地捕捉拍攝,不包括&。對於通過複製捕獲的每個實體,在封閉類型中聲明一個未命名的非靜態數據成員。 這些成員的聲明順序未指定。

強調添加。由於申報單未指定,因此施工單未指定(因爲施工順序與申報順序相同)。因此,訂單的銷燬順序是未指定的,因爲銷燬順序與構造順序相反。

總之,如果您需要關心報關單(以及關鍵的各種施工/銷燬訂單),您不能使用拉姆達。你需要製作自己的類型。

+1

難道你不能在lambda結束時重置其中一個共享指針,以確保它釋放資源在銷燬? –

+0

沒關係;看到我的答案。 lambda必須是可變的才能工作。 –

+0

@尼科爾:感謝您的澄清。我有些困惑,他們沒有指定一個訂單 - 通常在C++中,關於構造和銷燬順序的所有內容都是詳細說明的。但至少他們指定它沒有指定,所以不會依賴特定編譯器碰巧使用的順序。 –

3

根據我的C++ 11文檔(即免費贈品,稍早於批准n3242),第5.1.2節,第21段,捕獲按聲明順​​序構建,並以反向聲明順序進行破壞。但是,申報單沒有具體說明(第14段)。所以答案是,「按照未指定的順序」和「你不能影響它」(除非我想通過編寫一個編譯器)。

如果bar真的需要在foo之前被破壞,bar會持有指向foo的共享指針(或類似的東西)。

8

與其擔心銷燬的順序是什麼,你應該解決這個問題。注意到你正在爲這兩個對象使用共享指針,你可以通過在對象中添加一個共享指針來確保銷燬的順序,而這個指針需要超過另一個。在那個時候foobar是否被更早的銷燬並不重要。如果順序正確,銷燬共享指針將立即釋放對象。如果順序不正確,則額外的共享指針將維持該對象一直存在直到另一個消失。

8

正如尼科爾所說,銷燬的順序是未指定的。

但是,你不應該依賴於lambda的破壞。您應該可以簡單地在您的lambda結束時重置foo,從而確保它在bar之前釋放其資源。不過,您還必須將lambda標記爲mutable。這裏唯一的缺點是你不能多次調用lambda表達式並期望它能夠工作。

auto lambda = [foo, bar]() mutable { ...; foo.reset(); }; 

如果您確實需要你的拉姆達可以被調用多次,那麼你需要想出一些其他的方式來控制釋放的順序。一種選擇是使用一箇中間結構與已知的數據成員次序,諸如std::pair<>

auto p = std::make_pair(bar, foo); 
auto lambda = [p]() { auto foo = p.second, bar = p.first; ... }; 
+0

在我的情況下,lambda確實只能執行一次,所以你的手動重置方法應該可以正常工作。我忘記了爲了釋放shared_ptr指向的內容,您不一定需要將其釋放。非常感謝! –