C++11(二)
C++11 类型分类
C++11 以后,进一步对类型进行了划分,右值被细分为 纯右值 (prvalue) 和 将亡值 (xvalue)。
1. 纯右值 (pure value, 简称 prvalue)
- 定义:指那些字面值常量,或求值结果相当于字面值,或是一个不具名的临时对象。
- 等价关系:C++11 中的纯右值概念划分等价于 C++98 中的右值。
- 常见示例:
- 字面值:
42、true、nullptr - 不具名的临时对象或运算结果:
str.substr(1, 2)、str1 + str2 - 传值返回的函数调用
- 内置类型的运算:如整型
a、b,a++、a + b等
- 字面值:
2. 将亡值 (expiring value, 简称 xvalue)
- 定义:指返回右值引用的函数的调用表达式,以及转换为右值引用的转换函数的调用表达式。
- 常见示例:
std::move(x)static_cast<X&&>(x)
3. 泛左值 (generalized lvalue, 简称 glvalue)
- 定义:泛左值是一个更宽泛的概念,它包含了 将亡值 (xvalue) 和传统的 左值 (lvalue)。

C++ 引用折叠 (Reference Collapsing)
1. 基本概念与规则
- 背景:C++ 中不能直接定义“引用的引用”,例如
int& && r = i;会直接报错。但通过 模板 或 typedef 中的类型操作可以间接构成引用的引用。 - 折叠规则:当通过模板或 typedef 构成引用的引用时,C++11 给出了明确的折叠规则:
- 右值引用的右值引用 折叠成 右值引用 (
&& &&->&&)。 - 所有其他组合 均折叠成 左值引用 (
& &,& &&,&& &->&)。
- 右值引用的右值引用 折叠成 右值引用 (
2. typedef 与函数模板中的折叠演示
以下程序展示了在 typedef 和函数模板中,引用折叠的具体表现:
// 由于引用折叠限定,f1实例化以后总是一个左值引用
template<class T>
void f1(T& x) {}
// 由于引用折叠限定,f2实例化后可以是左值引用,也可以是右值引用
template<class T>
void f2(T&& x) {}
int main()
{
typedef int& lref;
typedef int&& rref;
int n = 0;
// --- typedef 中的折叠规则 ---
lref& r1 = n; // r1 的类型是 int& (左值引用的左值引用 -> 左值引用)
lref&& r2 = n; // r2 的类型是 int& (左值引用的右值引用 -> 左值引用)
rref& r3 = n; // r3 的类型是 int& (右值引用的左值引用 -> 左值引用)
rref&& r4 = 1; // r4 的类型是 int&& (右值引用的右值引用 -> 右值引用)
// --- 模板 f1 的折叠演示 ---
// 没有折叠 -> 实例化为 void f1(int& x)
f1<int>(n);
// f1<int>(0); // 报错,0为右值,无法传参
// 折叠 -> 实例化为 void f1(int& x)
f1<int&>(n);
// f1<int&>(0); // 报错
// 折叠 -> 实例化为 void f1(int& x)
f1<int&&>(n);
// f1<int&&>(0); // 报错
// 折叠 -> 实例化为 void f1(const int& x)
f1<const int&>(n);
f1<const int&>(0); // const左值引用可以正常引用右值
// 折叠 -> 实例化为 void f1(const int& x)
f1<const int&&>(n);
f1<const int&&>(0);
// --- 模板 f2 的折叠演示 ---
// 没有折叠 -> 实例化为 void f2(int&& x)
// f2<int>(n); // 报错,右值引用不可以引用左值
f2<int>(0);
// 折叠 -> 实例化为 void f2(int& x)
f2<int&>(n);
// f2<int&>(0); // 报错,0为右值,无法传参
// 折叠 -> 实例化为 void f2(int&& x)
// f2<int&&>(n); // 报错
f2<int&&>(0);
return 0;
}
万能引用 (Universal Reference)
在像 f2 这样的函数模板中,T&& x 参数看起来像是一个普通的右值引用参数,但由于引用折叠规则的存在,它的实际行为非常特殊:
- 当传递 左值 时,它就是 左值引用。
- 当传递 右值 时,它就是 右值引用。
因此,有些地方也把这种函数模板的参数叫做 万能引用。
推导原理
在 Function(T&& t) 函数模板程序中,其背后的推导逻辑如下:
- 实参为右值时
假设实参是int右值,模板参数T会被推导为int。结合引用折叠规则,最终实例化出 右值引用 版本的函数。 - 实参为左值时
假设实参是int左值,模板参数T会被推导为int&。结合引用折叠规则(int& &&->int&),最终实例化出 左值引用 版本的函数。
完美转发 (Perfect Forwarding)
1. 为什么要使用完美转发?
在 Function(T&& t) 函数模板程序中:
- 传左值实例化以后是左值引用的
Function函数。 - 传右值实例化以后是右值引用的
Function函数。
存在的问题:
变量表达式都是左值属性。这意味着一个右值被右值引用绑定后,右值引用变量(即函数形参 t)本身的属性是左值。如果我们直接把 t 传递给下一层函数 Fun,那么匹配的永远都是左值引用版本的 Fun 函数。
为了在转发过程中保持 t 对象原始的左右值属性,我们需要使用完美转发。
2. std::forward 的实现原理
std::forward 本质是一个函数模板,主要通过引用折叠的方式实现。其核心源码简化如下:
template <class _Ty>
_Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept
{
// 将参数无条件强转为 _Ty&&,利用引用折叠规则还原原始类型
return static_cast<_Ty&&>(_Arg);
}
C++11 可变参数模板总结
1. 基本语法及原理
C++11 支持可变参数模板,即支持可变数量参数的函数模板和类模板。可变数目的参数被称为参数包,主要分为两种:
- 模板参数包:表示零或多个模板参数。
- 函数参数包:表示零或多个函数参数。
2. 常见语法格式
使用省略号 ... 来指出一个模板参数或函数参数表示一个包。
// 基础语法
template <class ...Args> void Func(Args... args) {}
// 左值引用形式
template <class ...Args> void Func(Args&... args) {}
// 右值引用形式(完美转发常用)
template <class ...Args> void Func(Args&&... args) {}
获取参数个数
可以使用 sizeof… 运算符来计算参数包中参数的个数。
template <class ...Args>
void Print(Args&&... args)
{
cout << sizeof...(args) << endl;
}
int main()
{
double x = 2.2;
Print(); // 包里有0个参数
Print(1); // 包里有1个参数
Print(1, string("xxxxx")); // 包里有2个参数
Print(1.1, string("xxxxx"), x); // 包里有3个参数
return 0;
}
C++11 参数包的递归展开
在 C++11 中,参数包是不能直接用 for 循环遍历的(因为参数包的解析是编译时的行为,而 for 循环是运行时的逻辑)。因此,我们通常使用递归的方式,像“剥洋葱”一样把参数包里的元素一个个取出来。
实现递归展开通常需要三个部分:
- 外层接收函数:接收完整的参数包。
- 递归展开函数:每次提取参数包的第一个元素,剩下的继续递归。
- 终止函数:当参数包为空时,结束递归。
示例:打印参数包内容
#include <iostream>
#include <string>
using namespace std;
// 1. 递归的终止条件(当参数包被剥完,变为空时,匹配这个普通函数)
void ShowList() {
cout << endl; // 打印完所有参数后换行
}
// 2. 递归展开的主模板
// 每次取出一个参数 x,剩下的参数包是 args
template <class T, class ...Args>
void ShowList(T x, Args... args) {
cout << x << " "; // 处理当前拿到的第一个参数
ShowList(args...); // 触发包扩展:将剩下的参数包继续递归传递
}
// 3. 外层接收函数(可选,方便统一调用)
template <class ...Args>
void Print(Args... args) {
ShowList(args...); // 触发第一次包扩展
}
int main() {
Print(); // 输出空行
Print(1); // 输出:1
Print(1, string("hello")); // 输出:1 hello
Print(1, string("hello"), 3.14); // 输出:1 hello 3.14
return 0;
}
C++11 emplace 系列接口总结
1. 接口声明
template <class... Args> void emplace_back(Args&&... args);template <class... Args> iterator emplace(const_iterator position, Args&&... args);
2. 核心优势
- 原地构造:直接在容器内部内存中构造对象,避免了先构造临时对象再进行拷贝/移动的开销。
- 完美转发:传递参数包时必须使用
std::forward<Args>(args)...,防止右值引用在编译展开时退化为左值。
3. 模拟实现核心
在自定义容器的节点构造中,直接接收并完美转发参数包给数据类型 T 的构造函数:
// 节点的可变参数模板构造
template <class... Args>
ListNode(Args&&... args)
: _data(std::forward<Args>(args)...) // 核心:完美转发参数包
{}
// emplace_back 接口实现
template <class... Args>
void emplace_back(Args&&... args)
{
insert(end(), std::forward<Args>(args)...);
}
传左值/右值对象:emplace_back(obj) 等同于 push_back(obj),走拷贝/移动构造。
传构造参数包:emplace_back(args…) 是 push_back 做不到的,它能直接在容器内存中构造对象,效率最高。
更多推荐


所有评论(0)