C++11类型系统
C++ feels like a new language. -- Bjarne Stroustrup
- 类型推演
- 右值引用
- 通用引用
- 剖析std::move
- 剖析std::forward
- 总结
类型推演
增强了的「类型系统」是C++11
最大的优化亮点之一,为此需要深入剖析「类型推演」的工作机理,并能灵活地运用auto, decltype
,这是C++11
最重要的基石。
template与auto
C++98
早已具备类型推演的能力,用于模板的类型推演。
在C++11
中,auto
与template
的类型推演能力基本类似,只存在唯一的差异:Braced Initialization
,或称为Universal Initialization
。
非常量的左值引用
需要注意的是,推演auto &r2 = r, auto &cr2 = cr
时,即使r(int&), cr(const int&)
是引用变量,需要去除引用后再尝试类型推演,因为使用「引用变量」等价于使用其「引用对象」本身。
常量的左值引用
因为const T&
,const auto&
已经具备了const
的属性,当const
的左值对象赋予它所发生的自动类型推演,其模板参数T
,及其auto
的类型无需推演为const
属性。
指向非常量的指针
指针的推演能力与引用类似。
需要注意auto *p = &i; auto p = &i
两种写法的不一样,一种是显式的指针类型,另外一种完全依赖于auto
的类型推演能力。
指向常量的指针
与指向非常量的指针推演机制一致,在此不再冗述。
按值传递
Pass-By-Value
,经过拷贝之后,两者之间已无任何瓜葛,为此const
的处理机制有别于其他情况。
但存在两类特殊的,遗留的C-Style
情况,为保证兼容性,存在特殊的类型推演机制。
遗留的C-style
字符串
遗留的C-style
函数
通用引用: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
,与原来它持有右值刚好匹配。
通用初始化:Braced Initialization
这是template
与auto
类型推演能力之间存在的唯一差别。
右值引用
「右值引用」(Rvalue Reference)
与「通用引用」(Universal Reference)
是两个不同的概念,非常容易混淆,本文试图揭示两者之间的本质的差异。
左值与右值
「左值」与「右值」并非C++11
的产物,早已是C++
类型系统的一部分了,并且两者之间存在明显的区别。
举个例子,进一步明细两者之间的差异。此处使用auto&&
的Universal Reference
,它会根据「左值」自动推演为「左值引用」,而「右值」推演为「右值引用」。
右值引用
在C++98
中,只存在「左值引用」,遗恨缺失「右值引用」的概念,也因此丢失了部分性能优化的空间。C++11
中引入了「右值引用」,弥补之前的过失,结合「移动」(move)的机制,进一步提高了C++
在特殊场景的性能。
所谓右值引用,即「右值」的引用;之前惯称的「引用」,其实是「左值引用」的简称。「左值引用」只能引用「左值」,「右值引用」只能引用「右值」。
通用引用
「通用引用」并非「左值引用」,即使它们之间都具有&&
的语法结构。「通用引用」即可以持有「左值」,也可以持有「右值」,是一种「通用」的引用类型。而「右值引用」只能引用「右值」。
特征
-
void f(Object&& o)
,因为未发生类型推演,为右值引用 -
template <typename T> void f(std::vector<T>&& v)
,因为不是T&&
的句法结构,为右值引用
可以简单归纳之,「通用引用」出现于如下两种常见:
template <typename T>
void f(T&& t);
auto&& r = i;
样例
std::vector
新增加的「右值引用」的push_back
,及其「通用引用」的emplace_back
是最好的案例。
剖析std::move
C++11实现
当传递左值时
经过如下的类型推演过程,当传递「左值」时,std::move
强制转为换「右值引用」。
当传递右值时
经过如下的类型推演过程,当传递「右值」时,std::move
顺水推舟,传递「右值引用」。综上述,借助「通用引用」的能力,std::move
其实完成了「无条件的」右值引用转换规则。
C++14改进实现
剖析std::forward
C++11实现
当传递左值时
经过如下的类型推演过程得知,当传递「左值」时,std::forward
完成「左值」的「转发」机制。
当传递右值时
经过如下的类型推演过程得知,当传递「右值」时,std::forward
也完成「右值」的「转发」机制。为此,std::forward
的机制,完成了C++11
的「完美转换」(Perfect Forward
)的机制。