04區分通用引用和右值引用 Scott Meyers的effective modern c++講座摘要
阿新 • • 發佈:2019-03-29
.com all temp 對象 變量 圖片 做了 oca png
大師就是大師,可愛的蘑菇頭,講的太好了,很精彩!有激情,有耐心,很難解釋的東西隨手舉幾個例子就明白了!強烈推薦看看視頻!
https://www.youtube.com/playlist?list=PLmxXlAVb5hkyq5njldMEPYdOqTAQPLChR
核心問題
- 右值引用 一定是 type&&
- type&& 不一定是 右值引用(還有可能是通用引用)
先來看看,type&&的雙面性
void f(Widget&& param); // 右值引用 Widget&& var1 = Widget(); // 右值引用 auto&& var2 = var1; // 不是右值引用!!! decltype(var1) var3 = var1; // 無法編譯通過 Widget&& var4 = var1; // 無法編譯通過 template<typename T> void f(std::vector<T>&& param); // 右值引用 template<typename T> void f(T&& param); // 不是右值引用!!!
在這裏,"type&&"中的"&&"意味著:
- 右值引用
* 綁定右值(Binds rvalues only.)
* 促進移動(Facilitates moves.) - 通用引用
- 右值引用或左值引用(type&& 有可能是 type& 或 type&& )
- 綁定所有值,不管是左值,右值,const, 非const...
- 即促進拷貝,也促進移動(May facilitate copies, may facilitate moves)
- 與轉發引用(Forwarding Reference)相同
如何區分,簡單來說
- 如果一個變量或參數的聲明類型是T&&,並且需要推導出類型T, 這就是通用引用,否則就是右值引用。
- 通用引用是需要初始化的,如果是左值,那就是左值引用,如果是右值,那就是右值引用。
下面主要通過示例來解釋
template<typename T> void f(T&& param) // 典型的通用引用,T&& + 需要推導類型 Widget w; f(w); // w是左值,通用引用就是左值引用,f(Widget&) f(std::move(w)); // std::move無條件轉換為右值,所以通用引用就是右值引用,f(Widget&&) f(Widget()); // Widget()是右值,這也是右值引用,f(Widget&&) std::vector<int> v; ... auto&& val = 10; // 10是右值,所以通用引用就是右值引用,val的類型就是int&& auto&& element = v[5]; // v[5]返回的是int&, 所以這裏是左值引用,element的類型是int&
// 有類型推導才能是通用引用
void f(Widget&& w); // 沒有類型推導,所以這裏不是通用引用
template<typename T>
void f(T&& param); // 有類型推導,所以是通用引用
template<typename T>
class Gadget1 {
Gadget1(Gadget1&& rhs); // 沒有類型推導,不是通用引用
};
template<typename T1>
class Gadget2 {
tempalte<typename T2>
Gadget2(T2&& rhs); // 有類型推導,是通用引用
};
// 但是不是所有的T&&都是通用引用
template<class T,
class Allocator=allocator<T>>
class vector{
public:
...
// 這裏是右值引用!!! 因為T是從vecotr<T>來的,而不是通過push_back的參數傳遞來的
// 我解釋一下,這個T實際上是創建vector時就已經確定了,而不是等調用push_back時才做的類型推導
void push_back(T&& x);
...
};
std::vector<int> vi;
...
vi.push_back(10.5); // 實參類型是double, 但形參類型是int&&
Scott在這裏解釋的比較多,就是push_back為什麽要有一個通用引用的參數?
過去vector的push_back實際上會構造一個int, 然後放到vector中,這會導致效率問題,如果構造的是一個很大的對象,那麽每次調用push_back時都會調用拷貝構造函數,那麽就會導致效率直線下降,這也是造成過去標準庫讓人詬病的原因。
而現在引入右值引用,上面說過,是用於促進移動義的,因為10.5是一個右值,那麽在放到vector中時就會直接移動這個右值,而不會再構造一個int!想像一下,如果是一個大對象,那麽就會直接移動這個大對象,而不需要再調用拷貝構造函數,效率大大提升!
vector<BigObject> vi;
BigObject o;
vi.push_back(o); // 這是還是會調用BigObject的拷貝構造函數
vi.push_back(std::move(0)); // std::move無條件轉換為右值,這裏不會再調用拷貝構造函數,而是直接移動
vi.push_back(BigObject()); // 這裏也是右值,同上,不調用拷貝構造函數
最後比較一下push_back和emplace_back
從名稱來看,push就是推到最後,有點不論怎麽拿,各種暴力搬運,反正擱在最後就行了;而emplace就是放到最後,有點輕拿輕放的感覺。現在一般建議新代碼直接使用emplace_back。
template<class T, class Allocator=allocator<T>>
class vector{
public:
...
void push_back(const T& x); // 左值引用,拷貝值
void push_back(T&& x); // 右值引用,移動值
template<class... Args>
void emplace_back(Args&&... args); // 通用引用,轉發所有值
...
}
現在實現中,兩個push_back內部都是直接調用emplace_back, emplace_back是通用引用,所以可以接收任何值,無論左值還是右值。emplace_back使用變長參數,好處就是使用更加方便,比如:
class Widget
{
public:
Widget(int a, int b);
};
std::vector<Widget> v;
Widget w;
v.emplace_back(w); // 左值,拷貝值
v.emplace_back(std::move(w)); // 右值,移動值
v.emplace(Widget(10, 10)); // 右值,移動值
v.emplace(10, 10); // 右值,移動值,這裏使用了變長參數直接調用了構造函數,而push_back就不允許這樣做了
04區分通用引用和右值引用 Scott Meyers的effective modern c++講座摘要