右值
右值(rvalue)可以簡(jiǎn)單理解為賦值“=”語(yǔ)句中等號(hào)右側(cè)的變量,與之相對(duì)的為左值(lvalue)。7二者不同之處在于左值(lvalue)存儲(chǔ)在內(nèi)存中,具有固定的存儲(chǔ)地址;右值(rvalue)為字面量,存儲(chǔ)在寄存器中。
右值引用
常用的“&”引用為左值引用,下面代碼中a為左值,b為左值的引用。但是當(dāng)對(duì)右值10,取左值引用“&”編譯器會(huì)報(bào)錯(cuò)。
int a = 10;
int &b = a;
int &c = 10; // 錯(cuò)誤
但是可以通過常量引用對(duì)右值10進(jìn)行引用
const int& c = 10;
可以使用右值引用“&&”對(duì)右值進(jìn)行引用,右值引用后就有地址了
int &&c = 10;
std::cout << &c << std::endl;
move語(yǔ)義
簡(jiǎn)單來說 std::move 方法提供了一種將左值轉(zhuǎn)換為右值的方法。 被move的對(duì)象不再擁有對(duì)象的所有權(quán),將所有權(quán)轉(zhuǎn)移到接收引用的對(duì)象上。
萬能引用
萬能引用顧名思義可以處理各種情況下的引用,下面從最簡(jiǎn)單的值傳遞一點(diǎn)點(diǎn)解釋萬能引用的意義。考慮下面這個(gè)代碼。
void my_print(int i){
std::cout << i << std::endl;
}
當(dāng)傳遞的參數(shù)很大的時(shí)候,使用值傳遞會(huì)消耗資源,所以通常會(huì)采用引用的形式:
void my_print(int &i){
std::cout << i << std::endl;
}
采用引用進(jìn)行值傳遞后,避免了拷貝構(gòu)造的過程,但是正如前面所說,左值引用“&”不能接收右值,下面的調(diào)用編譯器會(huì)報(bào)錯(cuò)error: cannot bind non-const lvalue reference of type ‘int&’ to an rvalue of type ‘int’。
my_print(10);
使用常量引用"const &"可以解決這個(gè)問題:
void my_print(const int&i){
std::cout << i << std::endl;
}
但是使用常量引用后,因?yàn)橹付薱onst限定,就無法在原地對(duì)傳入的參數(shù)進(jìn)行修改了。下面這個(gè)函數(shù)定義會(huì)報(bào)錯(cuò)error: increment of read-only reference ‘i’。
void my_print(const int&i){
std::cout << ++i << std::endl;
}
為了減少值傳遞過程中的資源消耗,并且支持接收右值,同時(shí)保留對(duì)傳入?yún)?shù)修改的可能性。這個(gè)“既要又要還要”的經(jīng)典難題,可以通過實(shí)現(xiàn)多個(gè)重載來達(dá)到這個(gè)目的。
// 處理左值
void my_print(int &i){
std::cout << ++i << std::endl;
}
// 處理右值
void my_print(int &&i){
std::cout << ++i << std::endl;
}
當(dāng)期望使用右值版本的my_print時(shí),可以通過std::move將一個(gè)左值轉(zhuǎn)換為右值
int a = 10;
my_print(a); // 調(diào)用左值版本
my_print(std::move(a)); // 調(diào)用右值版本
my_print(10); // 調(diào)用右值版本
但是到這里又出現(xiàn)一個(gè)問題,當(dāng)傳入的參數(shù)為常量時(shí):
const int k = 10;
my_print(k);
編譯器找不到合適的重載函數(shù),會(huì)報(bào)錯(cuò)error: binding reference of type ‘int&’ to ‘const int’ discards qualifiers。為此,對(duì)于需要處理常量參數(shù)的情況(函數(shù)的內(nèi)容不再是“++”),還需要提供常量版本的重載。
為了通過一個(gè)函數(shù)完美解決以上所有通過引用參數(shù)傳遞帶來的問題(左值/右值引用,常量/非常量傳遞),可以使用下面給出萬能引用的參數(shù)傳遞版本,這個(gè)版本可以處理各種輸入情況,根據(jù)輸入?yún)?shù)不同屬性可以表現(xiàn)出相應(yīng)的行為。
template<typename T>
void my_print(T&& i){
std::cout << ++i << std::endl;
}
當(dāng)只有一個(gè)變量時(shí),萬能引用帶來的優(yōu)勢(shì)不大,但是當(dāng)存在多個(gè)參數(shù)變量時(shí),萬能引用可以節(jié)省大量重復(fù)代碼,提高編碼效率。
std::forward<T>
std::forward<T>是一個(gè)模板函數(shù),提供了一種轉(zhuǎn)發(fā)機(jī)制,該函數(shù)輸入右值輸出右值,輸入左值輸出左值。借助std::forward<T>可以實(shí)現(xiàn)完美轉(zhuǎn)發(fā),應(yīng)對(duì)各種不同的參數(shù)傳遞情況。
完美轉(zhuǎn)發(fā)
前面介紹的萬能引用可以處理不同類型的參數(shù)傳遞。但是作為函數(shù)的實(shí)現(xiàn)者,由于在實(shí)現(xiàn)過程中不知道傳入對(duì)象的語(yǔ)義,使用賦值語(yǔ)句和std::move都會(huì)改變對(duì)象的語(yǔ)義。所以為了不改變傳入對(duì)象的語(yǔ)義,使用std::forward<T>可以實(shí)現(xiàn)完美轉(zhuǎn)發(fā)。
最后給出一個(gè)c++標(biāo)準(zhǔn)庫(kù)中shared_ptr用到完美轉(zhuǎn)發(fā)的例子,方便理解:
template<class _Ty,
class... _Types>
_NODISCARD inline shared_ptr<_Ty> make_shared(_Types&&... _Args)
{ // make a shared_ptr
const auto _Rx = new _Ref_count_obj<_Ty>(_STD forward<_Types>(_Args)...); //參數(shù)完美轉(zhuǎn)發(fā)
shared_ptr<_Ty> _Ret;
_Ret._Set_ptr_rep_and_enable_shared(_Rx->_Getptr(), _Rx);
return (_Ret);
}
總結(jié)
綜上所述,我理解的右值和右值引用是c++為了使程序員可以更好的處理不在內(nèi)存中的字面值引入的概念。萬能轉(zhuǎn)發(fā)提供了一種范型編程的機(jī)制,可以應(yīng)對(duì)不同的參數(shù)類型和語(yǔ)義。std::move提供了一種將左值轉(zhuǎn)換為右值的途徑(賦值語(yǔ)句可以輕松的將右值轉(zhuǎn)換為左值)。std::forward<T>提供了借助轉(zhuǎn)發(fā)機(jī)制實(shí)現(xiàn)完美轉(zhuǎn)發(fā),應(yīng)對(duì)各種未知的參數(shù)傳遞情況。