C++11 概述
C++98 奠定了语言基础,而 2011 年发布的 C++11 标准则是一次里程碑式的更新。它不仅修复了早期版本的诸多缺陷,更引入了大量革命性特性,从根本上改变了 C++ 的编程范式,为开发者提供了更简洁、高效且安全的编码工具。
统一的列表初始化
C++11 扩大了大括号 {} 的使用范围,使其可用于所有内置类型和用户自定义类型。使用列表初始化时,可添加等号 =,也可省略。
int y = {2};
int y{2};
对于类对象,这支持多参数构造函数的隐式转换:
struct Point {
Point(int x, int y) : _x(x), _y(y) {}
int _x, _y;
};
Point p1 = {1, 1};
Point p2{1, 1};
// 等价于 Point p1(1, 1);
注意: 如果构造函数标记为 explicit,则禁止这种隐式转换。此外,在 new 表达式中也可以使用列表初始化:
Point* ptr3 = new Point[2]{{0, 0}, {1, 1}};
const Point& r = {3, 3}; // const 不能省略,因为创建的是临时对象
initializer_list
当 auto 用于初始化列表 {...} 时,编译器会优先推导出 std::initializer_list<T> 类型。一般容器的构造函数和赋值操作符都支持该类型。
vector<int> v1 = {1, 2, 3, 4, 3};
这里需要注意的是,{1, 2, 3, 4, 3} 转换成 initializer_list 时,编译器会在常量区自动创建一个隐藏的常量数组,initializer_list 只是用指针引用这个数组,而不是自己存储数据。因此,模拟实现该类型较为困难,通常依赖编译器支持。
声明优化
C++11 新增了 auto、nullptr 和 decltype 关键字,显著简化了类型声明。
nullptr vs NULL
NULL 有时被定义为 0 或 (void*)0,既能表示指针也能表示整型,存在歧义。nullptr 是专门的关键字,仅表示空指针。
#define NULL ((void*)0)
// 推荐直接使用 nullptr
auto 与 decltype
auto 不能单独使用(如 auto x;),必须配合初始化表达式。decltype 则可以将变量的类型声明为表达式指定的类型,常用于模板元编程或作为函数返回类型。
int a = 0;
double b = 1.0;
decltype(a * b) c = 2; // c 的类型为 double
int pf = 0;
decltype(pf) pf2; // 合法,pf2 类型为 int
STL 容器与接口更新
新容器
增加了 array、forward_list、unordered_map、unordered_set 等新容器。虽然 array 和 forward_list 在某些场景下不如 vector 通用,但在特定性能需求下仍有价值。
接口增强
- 初始化列表支持:所有容器的构造函数都支持
{...}方式初始化。 - 移动语义支持:所有容器都支持移动构造和移动赋值。
- emplace 系列:大多数容器接口新增
emplace_back等方法,直接在容器内部构造元素,避免临时对象的拷贝开销。 - 常量迭代器:添加了
cbegin,cend,crbegin,crend接口,用于获取只读迭代器。
类功能增强
C++11 允许在类定义时给成员变量赋予默认值,并引入了以下关键字:
- default:强制生成默认函数。例如:
Person(Person&& p) = default; - delete:禁止生成默认函数。例如:
Person(const Person&) = delete; - final:禁止类被继承或虚函数被重写。
- override:显式标记虚函数重写,防止拼写错误。
关于移动构造函数和移动赋值运算符重载:如果没有用户自定义析构函数、拷贝构造或拷贝赋值,编译器会自动生成默认移动版本。对内置类型执行浅拷贝,对自定义类型则调用其移动构造(若存在)或拷贝构造。
引用语义:左值、右值与移动
左值和右值
- 左值 (Lvalue):可以取地址的对象,通常是可以被修改的实体。
- 右值 (Rvalue):不能取地址的对象,通常是临时值。内置类型的右值称为纯右值,自定义类型的右值称为将亡值。
左值引用和右值引用
左值引用 (T&) 给左值取别名;右值引用 (T&&) 给右值取别名。右值引用引入后,对象实际上拥有了左值的属性(有地址,可修改),但语义上仍代表临时资源。
void func(const int& r); // 接收左值
void func(int&& r); // 接收右值
int a = 1;
func(a); // 调用第一个
func(2); // 调用第二个
右值引用的核心价值在于减少拷贝,特别是在传值返回或容器插入右值对象时,可以通过移动语义转移资源而非深拷贝。
移动构造
移动构造发生在将亡值赋值时。它不是简单的复制,而是'转移'资源所有权。原对象状态变为未定义(但不一定是空对象)。
string func() {
string str("xxxx");
return str; // 编译器优化:直接移动,避免拷贝
}
现代编译器会对连续构造或拷贝进行优化(RVO/NRVO),使得返回值可能只需一次移动拷贝。
完美转发
万能引用
万能引用 (T&&) 既可以接收左值,也可以接收右值。实参如果是左值,推导为左值引用;如果是右值,推导为右值引用。这是通过引用折叠规则实现的。
完美转发
在模板函数中,如果想保持参数的原始属性(左值还是右值),需要使用 std::forward 进行完美转发。否则,经过函数参数传递后,右值引用可能会退化为左值引用,导致无法触发移动语义。
template<typename T>
void PerfectForward(T&& t) {
Fun(std::forward<T>(t)); // 保留原始属性
}
如果在中间步骤丢失了一次转发,原本右值的属性就会永久丢失,导致后续操作只能进行拷贝而非移动。因此,在涉及资源转移的场景中,务必确保每次传参都正确使用完美转发。


