2014-12-29 21 views
3

我正在讀斯科特·梅耶的有效的現代C++,打在他的建議的lambda小號使用代替std::functionstd::bind的項目。我理解他的論點和他對std::function的缺點的主張,我同意他的觀點。存儲lambda表達式作爲成員混亂

截至今天,我決定切換到用於存儲lambda的模板(不需要增變器)。我明白每種lambda的類型只有編譯器才知道,甚至兩個相同的lambda也會有不同的類型,所以下面的代碼如何編譯並工作得很好?

template<typename LambdaT> 
class CaptureLambda 
{ 
public: 
    CaptureLambda(const LambdaT& fn) 
     : mActionFn(fn) // initialize mActionFn to a user supplied lambda 
    {} 

private: 
    LambdaT mActionFn{ []{} }; // initialize mActionFn to an empty lambda 
}; 

我困惑的一點就是,怎麼來mActionFn是默認啓動爲空拉姆達與不同類型的成員聲明內,但在類的構造函數中愉快地接受另一種類型的在它的參數lambda?他們是cast能夠彼此?如果是的話,爲什麼以下讓編譯器感到難過?

// Class stuff... 

template<typename T> 
void resetActionFn(const T& newFn) { // setter member 
    mActionFn = newFn; 
} 

// Class stuff... 

回答

4

非靜態數據成員初始化只有在構造函數沒有指定不同的初始化時纔會使用。

鑑於

struct S { 
    int i = 3; 
    S() : i(4) { } 
}; 

你沒有得到,首先初始化i3默認構造函數,然後重新初始化它4,你只拿到初始化i4構造。

這和你的班級一樣。您沒有任何構造函數不會初始化mActionFn,所以初始化程序不會被使用。

現在,正如Piotr S.在評論中指出的那樣,一般來說,首字母必須仍然在語義上有效,但是您的數據成員具有依賴類型,所以在模板定義時無法檢查有效性,並且初始化程序從未被實例化,因爲它沒有被使用,所以在實例化時錯誤也不被察覺。一個類似的簡單的例子是

template <typename T> 
struct S { 
    T x = 3; 
    S() : x(0) { } 
}; 
int main() { 
    S<void*>(); 
} 

其通過GCC默默接受即使3void*類型的字段無效初始化劑。鏗鏘拒絕它。該標準不清楚模板的實例化是否導致任何未使用的NSDMIs的實例化,並且一些編譯器僅在需要時實例化它們。 There is agreement that they should be instantiated only as needed, but there are some problems with that approach, which is why not all compilers implement that yet.

+0

這是否暗示用於nsdmi的表達式可能無效?我懷疑。就像你試圖擁有'std :: string s = 1;'一樣,它永遠不會被使用,因爲你在每個ctor中初始化's',所以你有 –

+0

@PiotrS。在一個模板類中,比特會根據需要進行實例化。例如,給定'template struct S {void f(){T(); }};',你可以有'S '類型的對象,你只能調用它的'f'成員函數。正確地說,哪些位應該在尚未解決的問題中進行實例化,並且存在一些編譯器變體。 – hvd

+0

,因爲類模板的非虛擬成員函數在上下文需要時被實例化。我認爲這不適用於數據成員。爲此提供標準參考將會很好。 –