2017-08-31 39 views
4

常量左值參考我最近一直在尋找到轉發引用C++和下面是我目前的概念的理解一個簡單的總結。轉發參考VS模板代碼

比方說,我有一個模板功能foo採取的轉發參考T類型的一個參數。

template<typename T> 
void foo(T&& arg); 

如果我把這種功能與左值然後T將推導出T&使arg參數是T&類型的由於參考塌陷規則T& && -> T&

如果該函數獲得與無名暫時的,稱爲諸如函數調用的結果,然後T將推導出T使arg參數是T&&類型。

Inside foo然而,arg是一個命名參數,因此如果我想將參數傳遞給其他函數並仍然保持其值類別,則需要使用std::forward

template<typename T> 
void foo(T&& arg) 
{ 
    bar(std::forward<T>(arg)); 
} 

據我瞭解,cv-qualifiers不受此轉發影響。這意味着,如果我調用foo與命名常量變量然後T將推導出const T&以及因此arg類型也將是const T&由於參考塌陷規則。爲常量右值T將推導出const T因此argconst T&&類型。

這也意味着如果我在foo中修改arg的值,如果我確實通過一個const變量傳遞給它,那麼我將得到一個編譯時錯誤。

現在到我的問題。 假設我正在編寫一個容器類,並且想要提供一個將對象插入到我的容器中的方法。

template<typename T> 
class Container 
{ 
public: 
    void insert(T&& obj) { storage[size++] = std::forward<T>(obj); } 
private: 
    T *storage; 
    std::size_t size; 
    /* ... */ 
}; 

通過使insert成員函數採取的轉發參考obj我可以使用std::forward如果insert是INFACT傳遞一個臨時對象取所存儲的類型T的移動賦值操作的優點。

以前,當我不知道任何關於轉發引用時,我會寫這個成員函數採用一個常量左值引用: void insert(const T& obj)

這樣做的缺點是,如果insert傳遞臨時對象,則此代碼不利用(假設更有效)移動賦值運算符。

假設我沒有錯過任何東西。

是否有任何理由,提供兩個重載插入功能?一個採取常數左值參考,另一個參考轉發參考。

void insert(const T& obj); 
void insert(T&& obj); 

我問的原因是the reference documentation for std::vector指出push_back方法有兩種重載。

void push_back (const value_type& val); 
void push_back (value_type&& val); 

爲什麼第一個版本(採取const value_type&)需要的?

+5

'insert(T && obj)'不是轉發引用。它由班級固定。 – Jarod42

回答

4

您必須小心功能模板,而非類模板的非模板方法。您的會員insert本身不是模板。這是一個模板類的方法。

Container<int> c; 
c.insert(...); 

我們可以很容易地看到,T不是推斷在第二行,因爲它已經固定int在第一行,因爲T是類的模板參數,而不是方法。

類模板的非模板方法,只有在類被實例化後纔會以一種方式區別於常規方法:除非實際調用它們,否則它們不會被實例化。這很有用,因爲它允許模板類使用類型,對於這些類型只有一些方法有意義(STL容器充滿了這樣的例子)。

的底線是,在我上面的例子中,由於T固定爲int,你的方法就變成了:

void insert(int&& obj) { storage[size++] = std::forward<int>(obj); } 

這不是一個forwaring參考可言,而只是通過右值引用需要,即它只綁定到右值。這就是爲什麼你通常會看到兩個重載,例如push_back,一個用於左值,另一個用於右值。

+0

非常清晰和簡潔的答案。我知道我錯過了一些東西!謝謝! – JonatanE

0

@Nir Friedman已經回答了這個問題,所以我會提供一些額外的建議。

如果您Container類並不意味着存儲多態類型(這是常見的容器,包括std::vector和其他類似STL容器),你可以簡化你的代碼,你正在試圖做的方式矇混過關你最初的例子。

相反的:

void insert(T const& t) { 
    storage[size++] = t; 
} 
void insert(T && t) { 
    storage[size++] = std::move(t); 
} 

您可以通過編寫得到完全正確的代碼,而不是執行以下操作:

void insert(T t) { 
    storage[size++] = std::move(t); 
} 

這樣做的原因是,如果對象是被複制的,t會使用提供的對象進行復制構建,然後移動分配到storage[size++],而如果對象正在移入,則t將與提供的對象一起移動構建,然後移動分配到storage[size++]。所以你已經簡化了你的代碼,只需要一個額外的移動分配代碼,許多編譯器會很高興地進行優化。但是,這種方法有一個主要缺點:如果對象定義了一個複製構造函數並且沒有定義一個移動構造函數(對於舊代碼中的舊類型是常見的),那麼這會導致所有的複製副本案例。您的編譯器可能能夠優化它(因爲編譯器可以優化爲完全不同的代碼,只要用戶可見的效果不變),但可能不會。如果你不得不使用不實現移動語義的重對象,那麼這可能是一個重大的性能問題。這可能是STL容器不使用這種技術的原因(他們重視性能而不是簡潔)。但是,如果您正在尋找一種方法來減少您編寫的樣板代碼的數量,並且不擔心必須使用「僅複製」對象,那麼這對您來說可能會很好。