C++ feels like a new language. -- Bjarne Stroustrup
- 类型推演
- 右值引用
- 通用引用
- 剖析std::move
- 剖析std::forward
- 总结
类型推演
增强了的「类型系统」是C++11
最大的优化亮点之一,为此需要深入剖析「类型推演」的工作机理,并能灵活地运用auto, decltype
,这是C++11
最重要的基石。
template与auto
C++98
早已具备类型推演的能力,用于模板的类型推演。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/86a209e7ccf263348b4f6eabc69eecb2.png)
在C++11
中,auto
与template
的类型推演能力基本类似,只存在唯一的差异:Braced Initialization
,或称为Universal Initialization
。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/3bbf094f69ad308adabb0a9150177170.png)
非常量的左值引用
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/993910a22517a3a16e14b674df91bfe3.png)
需要注意的是,推演auto &r2 = r, auto &cr2 = cr
时,即使r(int&), cr(const int&)
是引用变量,需要去除引用后再尝试类型推演,因为使用「引用变量」等价于使用其「引用对象」本身。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/a78a2f46b7302d6dce36cd9238d2b5bd.png)
常量的左值引用
因为const T&
,const auto&
已经具备了const
的属性,当const
的左值对象赋予它所发生的自动类型推演,其模板参数T
,及其auto
的类型无需推演为const
属性。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/88656fd68aa9a168610f422feb1e07fb.png)
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/6b1973467d339d3c20eebf3cd5719464.png)
指向非常量的指针
指针的推演能力与引用类似。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/114bdcbd18736bfd475636b03046b824.png)
需要注意auto *p = &i; auto p = &i
两种写法的不一样,一种是显式的指针类型,另外一种完全依赖于auto
的类型推演能力。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/0f76fbbb67f304c0506006a727b576a8.png)
指向常量的指针
与指向非常量的指针推演机制一致,在此不再冗述。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/69c715bb8c8a867479b999d52984492a.png)
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/d50f66ea156627a026b92321404f01f0.png)
按值传递
Pass-By-Value
,经过拷贝之后,两者之间已无任何瓜葛,为此const
的处理机制有别于其他情况。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/43b3b7e84d5be0cb349dd96ae47550c7.png)
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/9a87b700396e0eefd10637066e60ee23.png)
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/f7db6154f7155d1b2107966a819f6354.png)
但存在两类特殊的,遗留的C-Style
情况,为保证兼容性,存在特殊的类型推演机制。
遗留的C-style
字符串
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/14210ad7c10736ef401224f55b596767.png)
遗留的C-style
函数
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/5047138e8ce571528d669974c5002f04.png)
通用引用:Universal Reference
所谓Universal Reference
,因为其能Can bind to anything
,所以称为「通用引用」,具有如下方面的特点:
- Can bind to lvalue or rvalue;
- Can bind to const/non-const, volatile/non-volatile, or both;
- So, it can bind to anything.
需要注意的是,Universal Reference
并非「右值引用(Rvalue Reference)」,即使它们两者都有类似的T &&
的修饰符。规则非常简单,Universal Reference
具备两个最基本的特征:
-
T &&, auto&&
: 必须具备的句法结构 -
type reduce
:必须发生类型推演
可以简单归纳之,Universal Reference
出现于如下两种常见:
template <typename T>
void f(T&& t);
auto&& r = i;
Universal Reference
类型推演也存在特殊性:
Universal Reference
持有左值时,发生Reference Collapsing
机制。例如auto&& t = i
,当auto
推演为int&
,auto&& t
推演为int& && t
,而int& &&
经过Reference Collapsing
机制,被进一步规约为int&
,与原来它持有左值刚好匹配。Universal Reference
持有右值时,推演规则较为直观,例如auto&& t = 10
,当auto
被推演为int
,则auto&& t
推演为int&& t
,与原来它持有右值刚好匹配。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/88c584ad13ee2ce86dbb81506611e179.png)
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/1456e4cf854f6390a12309be38e82f48.png)
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/71e7606cec3f75cdc808055d0fcbfd9e.png)
通用初始化:Braced Initialization
这是template
与auto
类型推演能力之间存在的唯一差别。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/1d59c4baf98bab719596c46f98405245.png)
右值引用
「右值引用」(Rvalue Reference)
与「通用引用」(Universal Reference)
是两个不同的概念,非常容易混淆,本文试图揭示两者之间的本质的差异。
左值与右值
「左值」与「右值」并非C++11
的产物,早已是C++
类型系统的一部分了,并且两者之间存在明显的区别。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/1af5804c25ba3fd147d7306d81a6968e.png)
举个例子,进一步明细两者之间的差异。此处使用auto&&
的Universal Reference
,它会根据「左值」自动推演为「左值引用」,而「右值」推演为「右值引用」。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/09b79edc275713e10c8c74bdae18c934.png)
右值引用
在C++98
中,只存在「左值引用」,遗恨缺失「右值引用」的概念,也因此丢失了部分性能优化的空间。C++11
中引入了「右值引用」,弥补之前的过失,结合「移动」(move)的机制,进一步提高了C++
在特殊场景的性能。
所谓右值引用,即「右值」的引用;之前惯称的「引用」,其实是「左值引用」的简称。「左值引用」只能引用「左值」,「右值引用」只能引用「右值」。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/572f4650f809d592512e3fb84fe4f022.png)
通用引用
「通用引用」并非「左值引用」,即使它们之间都具有&&
的语法结构。「通用引用」即可以持有「左值」,也可以持有「右值」,是一种「通用」的引用类型。而「右值引用」只能引用「右值」。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/1f4f5438cd17623587a57cef1b35db7e.png)
特征
-
void f(Object&& o)
,因为未发生类型推演,为右值引用 -
template <typename T> void f(std::vector<T>&& v)
,因为不是T&&
的句法结构,为右值引用
可以简单归纳之,「通用引用」出现于如下两种常见:
template <typename T>
void f(T&& t);
auto&& r = i;
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/48fcb91d95e7ce53748baa777c5c1b08.png)
样例
std::vector
新增加的「右值引用」的push_back
,及其「通用引用」的emplace_back
是最好的案例。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/1ff3182be9a9649659fa03b74ca18cf2.png)
剖析std::move
C++11实现
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/af0d475f9371ff0926c4ee024a1c5320.png)
当传递左值时
经过如下的类型推演过程,当传递「左值」时,std::move
强制转为换「右值引用」。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/5e7c62b1dc79d1e8c1697ee9869624c4.png)
当传递右值时
经过如下的类型推演过程,当传递「右值」时,std::move
顺水推舟,传递「右值引用」。综上述,借助「通用引用」的能力,std::move
其实完成了「无条件的」右值引用转换规则。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/f90ac0de7462f15082d80872700694d5.png)
C++14改进实现
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/d4c8c9130707c02ba8ac846b5004eb1f.png)
剖析std::forward
C++11实现
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/ad2ee490afca262a660dc6c5d4ff4af8.png)
当传递左值时
经过如下的类型推演过程得知,当传递「左值」时,std::forward
完成「左值」的「转发」机制。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/40a4ad226c53e0ea020c9e07d395b344.png)
当传递右值时
经过如下的类型推演过程得知,当传递「右值」时,std::forward
也完成「右值」的「转发」机制。为此,std::forward
的机制,完成了C++11
的「完美转换」(Perfect Forward
)的机制。
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/977c268802d678ad4543bed5d9d634ef.png)
C++14改进实现
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/a1989e2fd36402cc8cbbf353986eda20.png)
习惯用法
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/d0d5ecb0866fb57d0585c49b94263377.png)
回顾
![](https://codingstyle-cn.b0.upaiyun.com/photo/2016/94463f0b79cd73b879da6fd54981edc3.png)