C++11:现代C++的里程碑
前言
C++11是C++语言发展史上的一个重要里程碑。从C++98到C++11,我们等了整整13年。C++11带来了约140个新特性,修正了约600个缺陷,让C++焕发出了新的生命力。本文将系统地介绍C++11中最实用、最常用的新特性,帮助大家快速掌握现代C++的精髓。
一、C++11简介
在2003年,C++标准委员会提交了一份技术勘误表(TC1),形成了C++03标准。但C++03主要是对C++98的漏洞修复,核心语言没有改动,所以人们习惯将两者合称为C++98/03标准。
C++11最初被命名为C++0x,其中"x"代表不确定的发布年份。从最初的C++07到C++08、C++09,最终在2011年才正式完成,因此定名为C++11。
相比于C++98/03,C++11更像是一门新语言:语法更加泛化和简化、更加稳定和安全、功能更强大,能显著提升程序员的开发效率。
二、统一的列表初始化
2.1 {}初始化
在C++98中,花括号{}只能用于数组或结构体的初始化。C++11大大扩展了它的使用范围:
cpp
// C++98的用法
int array1[] = {1, 2, 3, 4, 5};
struct Point { int x; int y; };
Point p = {1, 2};
// C++11扩展的用法
int x1{2}; // 内置类型
int array2[]{1, 2, 3, 4, 5}; // 数组
Point p2{1, 2}; // 结构体
int* pa = new int[4]{0}; // new表达式
// 自定义类型也可以使用列表初始化
class Date {
public:
Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}
private:
int _year, _month, _day;
};
Date d1{2022, 1, 1}; // 直接列表初始化
Date d2 = {2022, 1, 2}; // 拷贝列表初始化
2.2 std::initializer_list
std::initializer_list是C++11新增的类型,它让容器能够支持列表初始化:
cpp
// 查看initializer_list的类型
auto il = {10, 20, 30};
cout << typeid(il).name() << endl; // class std::initializer_list<int>
// STL容器支持列表初始化
vector<int> v = {1, 2, 3, 4};
list<int> lt = {1, 2};
map<string, string> dict = {{"sort", "排序"}, {"insert", "插入"}};
// 赋值也可以使用大括号
v = {10, 20, 30};
模拟实现支持initializer_list的vector:
cpp
namespace aramae {
template<class T>
class vector {
public:
vector(initializer_list<T> l) {
_start = new T[l.size()];
_finish = _start + l.size();
_endofstorage = _start + l.size();
T* vit = _start;
for (auto e : l) {
*vit++ = e;
}
}
vector<T>& operator=(initializer_list<T> l) {
vector<T> tmp(l);
swap(_start, tmp._start);
swap(_finish, tmp._finish);
swap(_endofstorage, tmp._endofstorage);
return *this;
}
private:
T* _start;
T* _finish;
T* _endofstorage;
};
}
std::initializer_list
本质上是编译器生成的"栈内存或静态内存的切片(Slice)"。它绝不动态分配堆内存,因此它的生命周期完全依赖于它所在的作用域(或静态存储期),绝不拥有数据的所有权,仅用于极轻量级的只读传递。
三、声明简化
3.1 auto
C++98中的auto是存储类型说明符,因为几乎没有实际价值而被废弃。C++11赋予了它全新的含义------自动类型推导:
cpp
int i = 10;
auto p = &i; // p的类型是int*
auto pf = strcpy; // pf的类型是函数指针
map<string, string> dict = {{"sort", "排序"}, {"insert", "插入"}};
auto it = dict.begin(); // 自动推导为 map<string,string>::iterator
// auto必须显式初始化
auto x; // 编译错误
3.2 decltype
decltype关键字用于声明表达式的类型,在模板编程中非常有用:
cpp
// 基本使用
const int x = 1;
double y = 2.2;
decltype(x * y) ret; // ret的类型是double
decltype(&x) p; // p的类型是const int*
// 在模板中的使用
template<class T1, class T2>
void F(T1 t1, T2 t2) {
decltype(t1 * t2) ret; // 推导出t1*t2的类型
cout << typeid(ret).name() << endl;
}

3.3 nullptr
由于C++中NULL被定义为字面量0,可能带来指针和整数的歧义问题。C++11引入了nullptr表示空指针:
cpp
// NULL的定义(在C++中)
#define NULL 0
void func(int) { cout << "int" << endl; }
void func(void*) { cout << "void*" << endl; }
func(NULL); // 调用func(int),可能不是我们期望的
func(nullptr); // 调用func(void*),明确表示空指针
四、范围for循环
范围for循环是C++11中非常实用的语法糖:
cpp
vector<int> v = {1, 2, 3, 4, 5};
// 只读访问
for (auto e : v) {
cout << e << " ";
}
// 修改元素
for (auto& e : v) {
e *= 2;
}
// C++17之后支持结构化绑定
map<string, int> m = {{"apple", 1}, {"banana", 2}};
for (auto& [key, value] : m) {
cout << key << ": " << value << endl;
}
五、智能指针
C++11引入了三种智能指针来管理动态内存,从根本上解决了内存泄漏问题:
| 智能指针 | 特点 | 使用场景 |
|---|---|---|
unique_ptr |
独占所有权,不能拷贝 | 明确独占资源所有权 |
shared_ptr |
共享所有权,引用计数 | 多个对象共享同一资源 |
weak_ptr |
弱引用,不增加引用计数 | 解决shared_ptr循环引用 |
cpp
// 基本使用
unique_ptr<int> up1(new int(10));
unique_ptr<int> up2 = make_unique<int>(20); // C++14
shared_ptr<int> sp1 = make_shared<int>(30);
shared_ptr<int> sp2 = sp1; // 引用计数增加
六、STL中的新变化

6.1 新增容器
C++11新增了几个重要的容器:
- array:固定大小的数组容器,比原生数组更安全
- forward_list:单向链表,比list更节省内存
- unordered_map / unordered_set:基于哈希表实现的无序关联容器,查找效率O(1)
cpp
// array的使用
array<int, 5> arr = {1, 2, 3, 4, 5};
cout << arr.size() << endl; // 5
cout << arr[0] << endl; // 1
// unordered_map的使用
unordered_map<string, int> um;
um["apple"] = 1;
um["banana"] = 2;
6.2 容器的新方法
大部分容器增加了cbegin()/cend()返回const迭代器,以及emplace系列函数:
cpp
vector<int> v = {1, 2, 3, 4};
// const迭代器
auto cit = v.cbegin();
// emplace_back:直接在容器中构造对象,避免拷贝
struct Person {
string name;
int age;
Person(string n, int a) : name(n), age(a) {}
};
vector<Person> persons;
persons.emplace_back("Alice", 25); // 直接构造
persons.push_back(Person("Bob", 30)); // 先构造再拷贝
七、右值引用和移动语义
传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。
什么是左值?什么是左值引用?
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。
什么是右值?什么是右值引用
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。
需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用,是不是感觉很神奇,了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。
7.1 左值与右值
左值 :可以取地址、可以出现在赋值号左边的表达式。
右值:不能取地址、不能出现在赋值号左边的表达式。
cpp
int a = 10; // a是左值,10是右值
int* p = &a; // &a合法
// int* p2 = &10; // 错误:不能对右值取地址
int b = a; // a是左值
int c = a + b; // a+b是右值
7.2 左值引用与右值引用
- 左值引用总结:
- 左值引用只能引用左值,不能引用右值。
- 但是const左值引用既可引用左值,也可引用右值
- 右值引用总结:
- 右值引用只能右值,不能引用左值。
- 但是右值引用可以move以后的左值。
cpp
// 左值引用
int a = 10;
int& ra = a; // ra是左值引用
const int& cra = 10; // const左值引用可以绑定右值
// 右值引用
int&& rr1 = 10; // rr1是右值引用
int&& rr2 = a + b; // 绑定临时对象
// int&& rr3 = a; // 错误:右值引用不能绑定左值
int&& rr4 = move(a); // 使用move将左值转为右值
左值引用的短板:
但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,
只能传值返回。例如:bit::string to_string(int value)函数中可以看到,这里只能使用传值返回,
传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。

右值引用和移动语义解决上述问题:
在bit::string中增加移动构造,移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不
用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。

7.3 移动语义的意义
移动语义通过"资源转移"而非"资源拷贝"来提高性能:
cpp
class MyString {
public:
// 移动构造:窃取资源,避免深拷贝
MyString(MyString&& other) noexcept
: _str(other._str), _size(other._size) {
other._str = nullptr;
other._size = 0;
}
// 移动赋值
MyString& operator=(MyString&& other) noexcept {
if (this != &other) {
delete[] _str;
_str = other._str;
_size = other._size;
other._str = nullptr;
other._size = 0;
}
return *this;
}
private:
char* _str;
size_t _size;
};
按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。C++11中,std::move()函数位于 头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。
STL容器插入接口函数也增加了右值引用版本:
http://www.cplusplus.com/reference/list/list/push_back/
http://www.cplusplus.com/reference/vector/vector/push_back/
7.4 完美转发
std::forward在模板中保持参数的左右值属性:
cpp
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t) {
Fun(std::forward<T>(t)); // 完美转发
}
int main() {
int a = 10;
PerfectForward(10); // 右值引用
PerfectForward(a); // 左值引用
PerfectForward(move(a)); // 右值引用
}
八、新的类功能
默认成员函数
原来C++类中,有6个默认成员函数:
- 构造函数
- 析构函数
- 拷贝构造函数
- 拷贝赋值重载
- 取地址重载
- const 取地址重载
最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。
C++11 新增了两个:移动构造函数和移动赋值运算符重载。
针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
8.1 默认成员函数控制
C++11允许显式控制默认函数的生成和删除:
cpp
class Person {
public:
// 强制生成移动构造
Person(Person&& p) = default;
// 禁止拷贝构造
Person(const Person& p) = delete;
// 禁止拷贝赋值
Person& operator=(const Person& p) = delete;
};
8.2 成员变量默认初始化
C++11允许在类定义时给成员变量设置默认值:
cpp
class Person {
private:
string _name = "Unknown"; // 默认值
int _age = 0;
vector<int> _scores = {1, 2, 3};
};
九、可变参数模板
可变参数模板允许函数或类接受任意数量的模板参数:
cpp
// 递归展开参数包
template<class T>
void ShowList(const T& t) {
cout << t << endl;
}
template<class T, class ...Args>
void ShowList(T value, Args... args) {
cout << value << " ";
ShowList(args...); // 递归调用
}
// 使用
ShowList(1, 'A', "hello", 3.14);
STL容器中的emplace系列函数就是利用可变参数模板实现的:
cpp
list<pair<int, string>> mylist;
mylist.emplace_back(1, "apple"); // 直接构造pair
mylist.push_back({2, "banana"}); // 先构造再移动
十、lambda表达式
10.1 基本语法
lambda表达式的格式:[capture-list] (parameters) mutable -> return-type { statement }
cpp
// 最简单的lambda
[]{}; // 什么都不做
// 基本使用
auto add = [](int a, int b) -> int { return a + b; };
cout << add(3, 4) << endl; // 7
// 省略返回值类型(编译器推导)
auto mul = [](int a, int b) { return a * b; };
// 捕获变量
int x = 10;
auto f1 = [x](int a) { return a + x; }; // 值捕获
auto f2 = [&x](int a) { return a + x; }; // 引用捕获
auto f3 = [=](int a) { return a + x; }; // 值捕获所有
auto f4 = [&](int a) { return a + x; }; // 引用捕获所有
10.2 实际应用
cpp
// 排序
vector<Goods> goods = {...};
sort(goods.begin(), goods.end(),
[](const Goods& g1, const Goods& g2) {
return g1._price < g2._price;
});
// 与STL算法配合
vector<int> v = {1, 2, 3, 4, 5};
int sum = 0;
for_each(v.begin(), v.end(), [&sum](int n) { sum += n; });
10.3 lambda的实现原理
lambda表达式在底层会被编译器转换为一个函数对象(仿函数)类:
cpp
// 源码中的lambda
auto lambda = [x](int a) { return a + x; };
// 编译器生成的等价代码
class __lambda {
private:
int x; // 捕获的变量
public:
__lambda(int _x) : x(_x) {}
auto operator()(int a) const { return a + x; }
};

十一、包装器
11.1 function包装器
std::function是一个通用的可调用对象包装器:
cpp
#include <functional>
// 普通函数
int f(int a, int b) { return a + b; }
// 函数对象
struct Functor {
int operator()(int a, int b) { return a + b; }
};
// 类成员函数
class Plus {
public:
static int plusi(int a, int b) { return a + b; }
double plusd(double a, double b) { return a + b; }
};
int main() {
// 包装普通函数
function<int(int, int)> func1 = f;
cout << func1(1, 2) << endl;
// 包装函数对象
function<int(int, int)> func2 = Functor();
cout << func2(1, 2) << endl;
// 包装lambda
function<int(int, int)> func3 = [](int a, int b) { return a + b; };
// 包装静态成员函数
function<int(int, int)> func4 = &Plus::plusi;
// 包装非静态成员函数(需要对象)
function<double(Plus, double, double)> func5 = &Plus::plusd;
cout << func5(Plus(), 1.1, 2.2) << endl;
}
function解决模板效率问题:
cpp
// 不使用function:模板会实例化多份
template<class F, class T>
T useF(F f, T x) {
static int count = 0;
cout << "count:" << ++count << endl;
return f(x);
}
// 使用function:只实例化一份
double f(double i) { return i / 2; }
struct Functor { double operator()(double d) { return d / 3; } };
int main() {
// 每次都实例化不同的模板
useF(f, 11.11);
useF(Functor(), 11.11);
useF([](double d){ return d/4; }, 11.11);
// 使用function包装,只实例化一次
function<double(double)> func1 = f;
function<double(double)> func2 = Functor();
function<double(double)> func3 = [](double d){ return d/4; };
useF(func1, 11.11);
useF(func2, 11.11);
useF(func3, 11.11);
}
11.2 bind绑定器
std::bind用于绑定函数参数,生成新的可调用对象:
cpp
int Plus(int a, int b) { return a + b; }
class Sub {
public:
int sub(int a, int b) { return a - b; }
};
int main() {
using namespace placeholders;
// 绑定所有参数
auto func1 = bind(Plus, 1, 2);
cout << func1() << endl; // 3
// 使用占位符
auto func2 = bind(Plus, _1, _2);
cout << func2(1, 2) << endl; // 3
// 调整参数顺序
auto func3 = bind(Plus, _2, _1);
cout << func3(1, 2) << endl; // 3 (相当于 Plus(2, 1))
// 绑定成员函数
Sub s;
auto func4 = bind(&Sub::sub, s, _1, _2);
cout << func4(5, 3) << endl; // 2
// 固定部分参数
auto func5 = bind(Plus, 10, _1);
cout << func5(5) << endl; // 15
}
十二、线程库
C++11正式将线程支持纳入标准库,使多线程编程不再依赖平台API。
12.1 thread类
cpp
#include <thread>
// 线程函数:函数指针
void ThreadFunc(int a) {
cout << "Thread: " << a << endl;
}
// 线程函数:函数对象
class TF {
public:
void operator()() {
cout << "Thread function object" << endl;
}
};
int main() {
// 方式1:函数指针
thread t1(ThreadFunc, 10);
// 方式2:lambda表达式
thread t2([](int a) { cout << "Thread: " << a << endl; }, 20);
// 方式3:函数对象
TF tf;
thread t3(tf);
// 等待线程结束
t1.join();
t2.join();
t3.join();
cout << "Main thread done!" << endl;
return 0;
}
12.2 线程参数传递
线程函数参数以值拷贝方式传递,需要使用std::ref传递引用:
cpp
void Func(int& x) {
x += 10;
}
int main() {
int a = 10;
// 错误:无法修改外部a
// thread t1(Func, a);
// 使用ref传递引用
thread t2(Func, std::ref(a));
t2.join();
cout << a << endl; // 20
// 成员函数作为线程函数,需要传递this
class MyClass {
public:
void process(int x) { /* ... */ }
};
MyClass obj;
thread t3(&MyClass::process, &obj, 100);
t3.join();
}
12.3 原子操作
使用原子类型避免多线程数据竞争:
cpp
#include <atomic>
atomic<int> counter{0};
void increment(int n) {
for (int i = 0; i < n; ++i) {
++counter; // 原子操作
}
}
int main() {
thread t1(increment, 1000000);
thread t2(increment, 1000000);
t1.join();
t2.join();
cout << counter << endl; // 2000000
}
12.4 互斥锁
使用lock_guard或unique_lock进行RAII式的锁管理:
cpp
#include <mutex>
mutex mtx;
int shared_data = 0;
void increment() {
lock_guard<mutex> lock(mtx); // RAII自动上锁解锁
++shared_data;
}
// unique_lock更灵活
void increment_with_timeout() {
unique_lock<mutex> lock(mtx, try_to_lock);
if (lock.owns_lock()) {
++shared_data;
}
}
12.5 条件变量
使用条件变量实现线程间同步:
cpp
#include <condition_variable>
void two_thread_print() {
mutex mtx;
condition_variable cv;
bool is_even = true;
int n = 100;
thread even([&]() {
for (int i = 0; i < n; i += 2) {
unique_lock<mutex> lock(mtx);
cv.wait(lock, [&]() { return is_even; });
cout << "Even: " << i << endl;
is_even = false;
cv.notify_one();
}
});
thread odd([&]() {
for (int i = 1; i < n; i += 2) {
unique_lock<mutex> lock(mtx);
cv.wait(lock, [&]() { return !is_even; });
cout << "Odd: " << i << endl;
is_even = true;
cv.notify_one();
}
});
even.join();
odd.join();
}
总结
C++11带来的新特性极大地提升了C++的开发效率和代码安全性:
| 特性类别 | 主要特性 | 核心价值 |
|---|---|---|
| 语法简化 | auto、decltype、范围for | 减少冗余代码 |
| 初始化 | 列表初始化、initializer_list | 统一初始化方式 |
| 资源管理 | 智能指针、移动语义 | 提升性能、避免内存泄漏 |
| 函数式编程 | lambda、function、bind | 更灵活的编程范式 |
| 并发编程 | thread、atomic、mutex | 标准化的多线程支持 |
参考资料:

