關於c++11轉發(std::forward)
c++11中增加了新特性,那就是引數的轉發。有時候我們希望在一個函式中呼叫另一個函式,如下列模板:
template <typename F,typename T1,typename T2>
void Func(F f,T1 t1,T2 t2){ f(t1,t2); }
如果我們需要的都是傳值的也好說,但是如果F這個函式傳的是引用,而t1,t2都是被拷貝過來的。
我們引用的是一個函式中的區域性變數,無法影響到真正想要影響的物件。
這裡有一個解決辦法,在函式模板推導模板實參時,由於引用疊加特性,我們對T1,T2宣告成為右值引用形式,那麼實參可以保證原來的狀態。
如:
template <typename F,typename T1 ,typename T2>
void Func(F f,T1 &&t1,T2 &&t2){ f(t1,t2);}
在上面的函式模板中,如果傳入t1位置的是個左值,那麼將是對t1的左值引用,如果他是右值,就是對他的右值引用。
這足以解決不少問題,但是離完美轉發還是差了一步!如果我們傳入一個右值,t1確實是右值引用,但是不幸的是他還是個變數,也就他依然是左值!
如果我們的f需要右值(引數是右值引用),我們也給他傳入了右值,但是依然會出錯,因為右值引用無法繫結到左值上。
原來的解決辦法是用std::move,他會返回右值,但是無論你原來是什麼型別,他都會返回右值,而我們這裡是要建立一個模板,是希望完美轉發!原來是左值,現在確是右值,
這還不如不用!畢竟左值的情況更多一點。
這時候就到forward發揮了,std::forward和move都定義在utility標頭檔案,他和std::move不同,他返回的是原來的屬性。而且他需要顯示指定模板實參,這麼說,看下面的例子吧
template<typename F,typename T1 ,typename T2 >
void Func(F f,T1 && t1;T2 && t2){ f(std::forward<T1>(t1),std::forward<T2>(t2)); }
上面的程式碼,就是我們想要的! 不過不是我這篇的主題。我的主題是c++太他媽叼了!為何?
首先這麼來看,只要進行到了Func體內,t1,t2就都是左值。forward何德何能,能知道他該轉換成什麼樣子??
注意到了forward的實參,雖然需要指定,但是實際上也是推匯出來的,因為他用了模板引數,這是根據實參推出來的。再加上引用疊加,似乎有辦法得到。
而且使用forward必須顯式指定模板,這他媽又有點意思,因為一般需要顯式指定的,是返回型別。所以我們推斷他的模板引數是用在返回型別身上了。實際上也
確實是這樣。forward<type>返回type &&。
這麼一看,似乎一切都有了答案,但是這個設計是在是叼的不行:
首先,根據引數傳入推導模板引數,如果傳入是左值(比如是int),那麼T1就是 左值引用(int &)。和右值引用疊加,成為左值引用。這樣,t1還是原來的t1。
forward<T1>返回的是 右值引用和左值引用的疊加(返回T1 &&,即int & + &&,也是int &),依然是左值。還是原來的t1。
如果傳入右值,那麼T1是該值原本屬於的型別,不加引用(int)。但是t1的確是個左值。不過這時候T1是int,返回的是int &&,也就是返回了右值引用。這是我們想要的。
確實是叼的不行。
現在對forward的疑惑就是引數型別是什麼了。打開了dev c++原始碼,發現實際上是這樣的:
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}
從上面看出來是個右值引用.....這他媽怎麼可以,如果是這樣,因為他的模板引數不是推導的,也就是是不能繫結左值的。這樣前面的一切都錯了。
後來查了vs的原始碼,才知道原來是有兩個過載。還有一個是type &。這樣解釋是合理的。但是如果是一個常量,那就無法傳入。我百思不得其解啊,雖然常量用的少
但也不能這樣對他!不過我試了下發現,常量是可以傳入的,what??????!!
跟著程式碼走一遍,才發現我忘了forward的使用環境了:傳入的是常量,那麼T1就是常量引用。疊加後,該返回什麼就返回什麼,完全不會影響到常量性質!因為需要常量的話,他的模板實參便是常量!!
至於為什麼forward要有右值引用的版本,這點我還真沒發現特別叼的用途。可能有時候std::forward也不一定非得用於轉發。他在外部也是可以實現move的功能的,不過在外部遠沒有std::move方便。至於為什麼不用const T&,這答案是顯而易見的,雖然確實什麼都可以傳進去,無論什麼量進去,都成了個常量,最後出來時候可能還要去const,還有很多麻煩,真寫成這樣就虧大了。
所以 ,把std::forward單獨拿出來,你會想,它有什麼叼用?可以實現move的功能,但是不光要顯式指定模板引數,還不支援常量!寫這個東西是幹嘛的!!
不過放到轉發裡,我操??完美!!這就是完美轉發!!這他媽誰設計的,我跪了。
搞清楚這一點,我徹底淪陷了,c++太他媽的吊了。我要當c++的腦殘粉。
同時,如果你覺得自己什麼地方都不如別人,可能你是std::forward,他是std::move。別灰心,可能你只是還沒找到位置啊哈哈!!