C++的萬能引用解析


C++11除了帶來了右值引用以外,還引入了一種稱為“萬能引用”的語法;通過“萬能引用”,對某型別的引用T&&,既可以表達右值引用,也可以表達左值引用。

定義

該語法有兩種使用場景,最常見的一種是作為函數模板的形參:

template<typename T>
void f(T&& param);

其中param就是一個萬能引用。
第二個場景則是auto聲明:

auto&& var2 = var1;

這兩種情況都涉及到了型別的推導,也就是說,如果你雖然遇到了T&&的形式,但是不涉及型別推導,那么它只是一個右值引用:

void f(Widget&& param);

同時,想要成為萬能引用,變量聲明的形式也必須正確無誤,必須正好是形如“T&&”才行。比如下面這些情況就不是萬能引用:

template<typename T>
void f1(std::vector<T>&& param); // param是一個右值引用

template<typename T>
void f2(const T&& param);      //param也是一個右值引用

如果你向f1和f2傳左值,會都是會直接報錯的。
除此之外,像下面這種情況也不是萬能引用:

template<class T, class Allocator=allocator<T>>
class vector{
public:
  void push_back(T&& x);
};

push_back的形參x的型別T是受vector影響的,假設給定T為Widget,那么就會被實例化為如下代碼:

template<class Widget, class Allocator=allocator<T>>
class vector{
public:
  void push_back(Widget&& x);
};

而作為對比,vector類中的emplace_back函數則是一個實實在在的萬能引用:

template<class T, class Allocator=allocator<T>>
class vector{
public:
  template<class...Args>
  void emplace_back(Args&&.. args);
};

args的型別Args完全獨立於vector的型別形參T,Args必須在每次調用emplace_back時被推導,因此args是萬能引用。

為什么是“萬能引用”?

你一定非常好奇,為什么這種形態被稱作“萬能引用”。原因正像前文所說的,通過“萬能引用”,對某型別的引用T&&,既可以表達右值引用,也可以表達左值引用。
比如說,傳入一個右值引用,一般都要給傳入的參數加一個std::move操作確保變量的可移動性:

class Widget
{
public:
  Widget(Widget&& rhs):name(std::move(rhs.name)){}
private:
  std::string name;
};

而萬能引用則有所不同,它一般是通過std::forward來進行轉換:

class Widget
{
public:
  template<typename T>
  void setName(T&& newName)
  {name = std::forward<T>(newName);}
private:
  std::string name;
};

在這種情況下,若newName是一個左值引用,則forward函數不會對它進行操作,它的返回值仍然是一個左值引用;若newName是一個右值,則會進行std::move的轉換。用戶使用時無需區分,這也正是它“萬能”之處。
舉個例子:

Widget w;
std::string c = "123";
w.setName(w);            //傳入是一個左值,forward返回左值引用
w.setName("123");        //傳入是一個右值,forward返回右值引用
w.setName(std::move(w)); //傳入是一個右值,forward返回右值引用

引用折疊

你一定會很奇怪,為什么萬能引用的形式明明是T&&,卻既可以代表左值又可以代表右值。這就要涉及到C++的引用折疊語法了。
首先,C++不支持“引用的引用”這種概念,這樣的代碼在C++中是非法的:

int x;
auto& & rx = x; 

但是,假設我們向前面的萬能引用函數f傳入一個左值引用:

Widget w = w;
f(w);  //T的推導型別為Widget&

那么實例化的結果如下:

void f(Widget& && param);

之所以這樣的代碼能通過,是因為在特殊的情況下(比如模板實例化),C++應用了引用折疊的語法。
有左值和右值兩種引用,所以就有四種可能的組合:左值-左值、左值-右值、右值-左值、右值-右值,如果引用的引用出現在允許的語境,改雙重引用會被折疊成單個引用:

如果任一引用為左值引用,則結果為左值引用。否則(即兩個都為右值引用),結果為右值引用。

因此上述例子中,最終將param推導為左值引用。
此外,auto的型別推導也會應用引用折疊的場景,例如:

Widget w;
auto &&w1 =w;  //w1是個左值

話說到這里,我們就可以更深入地理解萬能引用,其實它就是滿足了下面兩個條件的語境中的右值引用:
1.型別推導的過程會區別左值和右值。T型別的左值推導結果為T&,而T型別的右值推導結果為T。
2.會發生引用折疊。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM