2016-12-02 115 views
10

分配通用構造函數成員變量時請考慮以下的類foo1foo2的std ::移動或std ::向前C++

template <typename T> 
struct foo1 
{ 
    T t_; 

    foo1(T&& t) : 
     t_{ std::move(t) } 
    { 
    } 
}; 

template <typename T> 
struct foo2 
{ 
    foo1<T> t_; 

    foo2(T&& t) : 
     t_{ std::forward<T>(t) } 
    { 
    } 
}; 

是它總是的foo1構造代表的正確方法的情況下初始化成員變量T?即通過使用std::move

由於需要轉發到foo1的構造函數,構造函數foo2是否代表初始化成員變量foo1<T>的正確方法?即通過使用std::forward

更新

下面的示例使用std::movefoo1爲失敗:

template <typename T> 
foo1<T> make_foo1(T&& t) 
{ 
    return{ std::forward<T>(t) }; 
} 

struct bah {}; 

int main() 
{ 
    bah b; 

    make_foo1(b); // compiler error as std::move cannot be used on reference 

    return EXIT_SUCCESS; 
} 

因爲我想噸至既引用類型和值類型這是一個問題。

+2

我會讓知道背後原因的人發佈一個完整的答案,但如果你想快速的是/否,我總是被告知你的'foo2'例子是正確的。 –

+1

這些不是「通用」(轉發)引用 - 如果您需要轉發引用,則需要模板化構造函數。 – Holt

+0

這裏沒有任務。 –

回答

10

這些示例都不使用通用引用(轉發引用,因爲它們現在稱爲)。

轉發引用僅在類型推演存在的情況下形成,但foo1foo2的構造函數中的T&&未被推導,因此它只是右值引用。

由於二者都是右值引用,所以兩者都應該使用std::move

如果你想使用轉發引用,你應該做的建設者有一個推導模板參數:

template <typename T> 
struct foo1 
{ 
    T t_; 

    template <typename U> 
    foo1(U&& u) : 
     t_{ std::forward<U>(u) } 
    { 
    } 
}; 

template <typename T> 
struct foo2 
{ 
    foo1<T> t_; 

    template <typename U> 
    foo2(U&& u) : 
     t_{ std::forward<U>(u) } 
    { 
    } 
}; 

你不應該在foo1在這種情況下使用std::move,作爲客戶端代碼可以通過左值和有對象無效默默地:

std::vector<int> v {0,1,2}; 
foo1<std::vector<int>> foo = v; 
std::cout << v[2]; //yay, undefined behaviour 

一個更簡單的方法是通過值取和無條件std::move到存儲:

template <typename T> 
struct foo1 
{ 
    T t_; 

    foo1(T t) : 
     t_{ std::move(t) } 
    { 
    } 
}; 

template <typename T> 
struct foo2 
{ 
    foo1<T> t_; 

    foo2(T t) : 
     t_{ std::move(t) } 
    { 
    } 
}; 

完美轉發版本:

  • 傳遞左值 - >一個副本
  • 傳遞右值 - >一招

對於傳值和移動版本:

  • 通過左值 - >一個副本,一次移動
  • 傳遞右值 - >兩個動作

考慮這個代碼需要怎樣高性能是,它會需要多少改變和維護,並選擇基礎上的選項。

+0

_「在這種情況下,您不應該在foo1中使用std :: move,如果構造函數傳遞了非const的左值,它將無法編譯。」_爲什麼? –

+0

@LightnessRacesinOrbit哎呦,修正了,謝謝。 – TartanLlama

+0

感謝您的回答,這是否意味着我會在這種情況下使用'std :: move':'template auto make_foo1(T && t){return foo1 {std :: move(t)}; }'因爲沒有類型扣除? – keith

0

這取決於你如何演繹T。例如:

template<class T> 
foo1<T> make_foo1(T&& t) { 
    return std::forward<T>(t); 
} 

在這種情況下,在foo1<T>T是轉發引用,您的代碼將無法編譯。

std::vector<int> bob{1,2,3}; 
auto foo = make_foo1(bob); 

上面的代碼從bob默默地移入std::vector<int>&構造內foo1<std::vector<int>&>

這樣做與foo2一樣可行。你會得到一個foo2<std::vector<int>&>,它將持有對bob的參考。

當你寫一個模板時,你必須考慮T這個類型是什麼意思。如果您的代碼不支持將其作爲參考,請考慮使用static_assert或SFINAE來阻止該情況。

template <typename T> 
struct foo1 { 
    static_assert(!std::is_reference<T>{}); 
    T t_; 

    foo1(T&& t) : 
    t_{ std::move(t) } 
    { 
    } 
}; 

此代碼生成一個合理的錯誤消息。

你可能會認爲現有的錯誤信息是好的,但它只是好的,因爲我們搬進了T

template <typename T> 
struct foo1 { 
    static_assert(!std::is_reference<T>{}); 

    foo1(T&& t) 
    { 
    auto internal_t = std::move(t); 
    } 
}; 

這裏只static_assert確保我們T&&是實際右值。


但足夠與這個理論列表的問題。你有一個具體的。

到底這可能是想你想:

template <class T> // typename is too many letters 
struct foo1 { 
    static_assert(!std::is_reference<T>{}); 
    T t_; 

    template<class U, 
    class dU=std::decay_t<U>, // or remove ref and cv 
    // SFINAE guard required for all reasonable 1-argument forwarding 
    // reference constructors: 
    std::enable_if_t< 
     !std::is_same<dU, foo1>{} && // does not apply to `foo1` itself 
     std::is_convertible<U, T> // fail early, instead of in body 
    ,int> = 0 
    > 
    foo1(U&& u): 
    t_(std::forward<U>(u)) 
    {} 
    // explicitly default special member functions: 
    foo1()=default; 
    foo1(foo1 const&)=default; 
    foo1(foo1 &&)=default; 
    foo1& operator=(foo1 const&)=default; 
    foo1& operator=(foo1 &&)=default; 
}; 

或者,這只是在99/100情況一樣好簡單的情況:

template <class T> 
struct foo1 { 
    static_assert(!std::is_reference<T>{}); 
    T t_; 

    foo1(T t) : 
    t_{ std::move(t) } 
    {} 
    // default special member functions, just because I never ever 
    // want to have to memorize the rules that makes them not exist 
    // or exist based on what other code I have written: 
    foo1()=default; 
    foo1(foo1 const&)=default; 
    foo1(foo1 &&)=default; 
    foo1& operator=(foo1 const&)=default; 
    foo1& operator=(foo1 &&)=default; 
}; 

作爲一般規則,這更簡單的技術比完美的轉發技術的結果只有更多的代價和複雜性。它允許{}初始化T t參數給你的構造函數,這很好。