2013-12-16 55 views
10

關於std::forward的建議通常限於完美轉發函數模板參數的規範用例; some commentators竟然說這是只有有效使用std::forward。但考慮這樣的代碼:std ::轉發沒有完美轉發?

// Temporarily holds a value of type T, which may be a reference or ordinary 
// copyable/movable value. 
template <typename T> 
class ValueHolder { 
public: 
    ValueHolder(T value) 
    : value_(std::forward<T>(value)) { 
    } 


    T Release() { 
    T result = std::forward<T>(value_); 
    delete this; 
    return result; 
    } 

private: 
    ~ValueHolder() {} 

    T value_; 
}; 

在這種情況下,完美轉發的問題不會出現:因爲這是一個類模板而不是函數模板,客戶端代碼必須明確指定T,並且可以選擇是否和如何重新鑑定它。同樣,std::forward的論點不是「通用參考」。

儘管如此,std::forward似乎這裏是一個不錯的選擇,我們不能就這麼走了出來,因爲當T是唯纔是舉型這是行不通的,我們不能用std::move因爲不會當T是一個左值引用類型時工作。當然,我們可以部分專用ValueHolder來使用參考的直接初始化和std::move的值,但是當std::forward完成這項工作時,這看起來過於複雜。對於std::forward的含義,這似乎也是一個合理的概念匹配:我們試圖一般性地轉發可能或可能不是參考的東西,只是將它轉發給函數的調用者,而不是函數稱自己。

這是一個健全的使用std::forward?有什麼理由避免它?如果是這樣,那麼最好的選擇是什麼?

+1

'std :: forward',在這種情況下,沒有任何意義。 'T'是類模板的參數,而不是構造函數/函數。當你讓編譯器推導出類型時,'std :: forward'是有意義的,在函數模板的情況下這是可能的。 – Nawaz

+1

如果參數是T && – polkadotcadaver

+0

,這隻有一點意義。如果您願意,這是一個完美轉發的擴展案例。它變得更清晰,如果你有一個'template ValueHolder make_holder(T && v){return {std :: forward (v)}; }工廠功能。用戶提供的'T'還是通過工廠函數是無關緊要的。 (儘管我建議不要刪除這個部分。) – Xeo

回答

5

std::forward是一個有條件的move -cast(或更技術上右值演員),沒有更多,沒有什麼比。雖然在完美的轉發環境之外使用它是令人困惑的,但如果move上的相同條件適用,則它可以做正確的事情。

即使在forward上只有一個薄包裝,我也會試圖使用不同的名稱,但對於上述情況我想不出一個好的包裝。

或者使條件移動更明確。即,

template<bool do_move, typename T> 
struct helper { 
    auto operator()(T&& t) const 
    -> decltype(std::move(t)) 
    { 
     return (std::move(t)); 
    } 
}; 
template<typename T> 
struct helper<false, T> { 
    T& operator()(T&& t) const { return t; } 
}; 
template<bool do_move, typename T> 
auto conditional_move(T&& t) 
->decltype(helper<do_move,T>()(std::forward<T>(t))) 
{ 
    return (helper<do_move,T>()(std::forward<T>(t))); 
} 

這是一個空操作,直通如果bool是假的,並move如果爲真。然後,您可以使您的等價物更加明確,並且不太依賴用戶理解C++ 11的奧祕祕密來理解您的代碼。

用途:

std::unique_ptr<int> foo; 
std::unique_ptr<int> bar = conditional_move<true>(foo); // compiles 
std::unique_ptr<int> baz = conditional_move<false>(foo); // does not compile 

在第三手,那種你在上面做的事之類的,需要右值和左值語義的合理深刻的理解,所以也許forward是無害的。

+1

'std :: forward'實際上是這裏的正確工具。你可以把它變成'conditional_move <!std :: is_lvalue_reference :: value>(value);'但這只是掩蓋和過度複雜的東西。它是一種轉發形式,是完美轉發的一種修改形式(因爲它移動而不是將東西保留爲右值)。 – Xeo

+0

如何在原始示例中使用'conditional_move'實現'ValueHolder'?特別是,我將如何確定通過「do_move」的參數?我毫不懷疑它可以完成,但它似乎會增加更多的複雜性,並且相當容易失敗。 DRY原則似乎也反對在從「T」推斷時明確指定「do_move」。 –

+0

@GeoffRorner參見上面的Xeo註釋:它簡化爲'std :: forward',正如你所說的那樣,你的預期邏輯與'std :: forward'相同,除了它不被稱爲'std :: forward'和條件(在其下移動)是明確的。您可以將邏輯從表達式中除去,例如'static constexpr bool should_move =!std :: is_lvalue_reference :: value;',然後使用'conditional_move (blah)'。 – Yakk

0

只要正確使用,這種包裝工作。 std::bind做了類似的事情。但是這個類也反映了當吸氣者進行移動時它是一次性功能。

ValueHolder是一個用詞不當,因爲它明確支持引用,這是與值相反的。 std::bind採用的方法是忽略左值和應用值語義。要獲得參考,用戶應用std::ref。因此,引用通過統一的值語義接口來建模。我也使用了bind的自定義一次性rref類。

在給定的實現中有幾個不一致。由於名稱result是左值,因此return result;將在右值引用專用中失敗。那裏你需要另一個forward。另外constRelease的合格版本會刪除自身並返回存儲值的副本,這將支持常量合格但動態分配的對象的偶然情況。 (是的,您可以delete a const *。)

另外,請注意裸引用成員呈現類不可分配。 std::reference_wrapper也適用於此。

至於使用forward本身,它遵循其公佈的目的,只要它根據爲原始源對象推導的類型傳遞一些參數引用。這需要額外的步法,大概在未顯示的課程中。用戶不應該寫一個明確的模板參數......但最後,只要沒有錯誤,它就是主觀的,好的,只是可以接受的,或者是駭人聽聞的。

+0

'ValueHolder'不會包含右值引用,我想。它要麼保持一個'T'或'T&'(即一個值或一個左值引用)。因此,僅僅是「返回值」是正確的。 – Xeo