之前介紹過std::move
未巫,今天我們就接著來說說std::forward
。C++11引入了一個新特性:右值引用启昧,這個特性可以避免不必要的拷貝從而提高性能叙凡。
std::forward
我們先看看std::forward
是干什么的,然后說說為什么需要它箫津。
根據(jù)前文狭姨,資源高效轉(zhuǎn)移的問題不是已經(jīng)有std::move
來解決了么,為什么還需要另外一個std::forward
來參和苏遥?注意“不必要”這個詞,既然有不必要赡模,那么就說明有時候是有必要的田炭。
雖然std::move
和std::forward
都和右值引用有關,但是側(cè)重點不同漓柑。std::move
用在需要只右值引用的地方教硫;而std::forward
用在一個需要統(tǒng)一引用(universal references)的地方叨吮,這個通用引用是什么?我更喜歡叫它薛定諤的引用瞬矩,因為它到底是左值引用還是右值引用是不確定的茶鉴,如果你給他傳遞左值它就是左值引用,如果給它傳個右值它就是右值引用景用。形如
template<typename T>
T f(T&& param) {}
這種T&&
就是通用引用涵叮。
假設我們有下面這么一個類,Foo
伞插,在使用的過程中會出現(xiàn)以下兩種初始化方式:
class Foo {
public:
std::string member_;
Foo(const std::string& member): member{member} {}
}
// Two use cases
// Case#1
std::string bar = "bar";
Foo foo(bar);
// Case#2
std::string bar = "bar";
Foo foo("foo" + bar);
這兩種方式有什么不同呢割粮?第一種使用場景中,我們已經(jīng)有了一個字符串"bar"
和引用bar
綁定在了一起媚污,我們希望用它來初始化Foo
舀瓢,但是這個bar
我們后續(xù)還需要使用,所以我們希望它拷貝一份給Foo
耗美;第二種情況中京髓,"foo" + bar
這個表達式生成了一個臨時字符串"foobar"
,由于它是臨時的商架,外部是沒有任何引用和他綁定的堰怨,很快就會被銷毀,因此我們希望能將它的內(nèi)存資源直接轉(zhuǎn)移給Foo
而不是拷貝一份甸私。鑒于存在上述兩種使用場景诚些,常規(guī)情況下我們需要分別定義兩個構造函數(shù):
class Foo
{
public:
std::string member;
// Copy member.
Foo(const std::string& member): member{member} {}
// Move member.
Foo(std::string&& member): member{std::move(member)} {}
};
但是我們懶,不想寫那么多構造函數(shù)皇型,有沒有辦法實現(xiàn)诬烹?有。我們使用std::forward
:
class Foo
{
public:
std::string member;
template<typename T>
Foo(T&& member): member{std::forward<T>(member)} {}
};
如果上面不夠清晰的話弃鸦,我們來看看下面這個例子:
#include <iostream>
#include <string>
#include <utility>
void foo(std::string& param) {
std::cout << "std::string& version" << std::endl;
}
void foo(std::string&& param) {
std::cout << "std::string&& version" << std::endl;
}
template<typename T>
void wrapper(T&& param) {
// foo(param);
foo(std::forward<T>(param));
}
int main() {
std::string foo("foo");
wrapper(foo);
wrapper(foo + "bar");
}
再上面的例子中绞吁,如果在wrapper
中沒有使用std::forward
,也就如果使用注釋掉的那個方法調(diào)用foo
函數(shù)唬格,得到的結(jié)果將是這樣子:
std::string& version
std::string& version
而如果使用目前的方式調(diào)用foo
家破,結(jié)果將是:
std::string& version
std::string&& version
std::forward
到底做了什么?
它主要作用如下:根據(jù)模板參數(shù)T
购岗,將模板函數(shù)的形參param
變成在右值傳遞給函數(shù)foo
或者將param
保留為左值傳遞給函數(shù)foo
汰聋。什么意思呢?就是如果傳遞個形參param
的值是左值喊积,例如上面例子中的foo
烹困,那么std::forward
返回的是一個左值;果傳遞個形參param
的值是右值乾吻,例如上面例子中的表達式foo + "bar"
得到的是一個右值髓梅,那么std::forward
返回的是一個右值拟蜻。因為根據(jù)C++語義,在函數(shù)wrapper
的內(nèi)部枯饿,param
是一個左值引用酝锅。
總的一句話就是std::forward
能夠保留傳給形參param
的實參的全部信息。wrapper(foo);
中參數(shù)foo
是左值奢方,那么wrapper
傳給函數(shù)foo
的就是左值搔扁;wrapper(foo + "bar");
中參數(shù)foo + "bar"
是右值,那么wrapper
傳給函數(shù)foo
的就是右值袱巨。
但是阁谆,std::forward
是怎么知道一個形參的原本類型的呢?這里又引出兩個知識點:模板參數(shù)類型推導( template argument deduction)和引用則疊(Reference collapsing)
引用則疊和模板參數(shù)類型推導
關于引用則疊和模板參數(shù)推斷愉老,可以說上一天场绿,所以這里步打算展開,僅僅簡單介紹下什么是引用則疊嫉入。假設有下面這種情況:
// T denotes the int& type
typedef int& T;
// TR is an lvalue reference to T
typedef T& TR;
// The declared type of var is TR
TR var;
變量var
的類型是TR
焰盗,而TR
是類型T
的移用,T
右是int
類型的一個引用咒林,這樣一串下來熬拒,var
的真實類型是什么呢?
引用的引用垫竞,不管是左值引用還是右值引用澎粟,在C++11之前是非法的,但是上面例子中的這種情況又是很可能出現(xiàn)的欢瞪。為了解決這一問題活烙,C++11定義了一套規(guī)則去處理引用的引用是什么的問題,這就是移用則疊遣鼓。
引用則疊主要右以下四條規(guī)則:
T | TR | Type of var |
---|---|---|
A& | T& | A& |
A& | T&& | A& |
A&& | T& | A& |
A&& | T&& | A&& |
套用到上面的小例子啸盏,var
的類型就是一個int&
。那這個到底和std::forward
有什么關系呢骑祟?這里留下一個坑回懦,關于模板參數(shù)類型,以后有機會再說啦次企。
總結(jié)
std::forward
與std::move
一樣怯晕,都與C++11引入的新特性右值引用相關。但是缸棵,與std::move
不同的是贫贝,std::forward
可以將參數(shù)保留它的類型信息,原樣轉(zhuǎn)發(fā)給下一個被調(diào)用的函數(shù)蛉谜。實現(xiàn)這一動作的原理是模板參數(shù)推導和引用則疊稚晚。
References
[1] ppreference.com
[2] Perfect Forwarding in C++11
[3] Reference collapsing (C++11)
[4] Advantages of using forward