C++11 是一个里程碑式的标准,它让 C++ 变得更现代、更安全、更高效。可以说,掌握 C++11 的新特性,是写出优雅、高性能 C++ 代码的关键。下面我会分门别类,把最常用、最核心的特性试着讲清楚。
1. 类型推导:auto 与 decltype
1.1 auto 关键字
以前声明变量必须明确写出类型,现在可以让编译器自动推导:
auto x = 42; // int
auto y = 3.14; // double
auto s = std::string("hello"); // std::string
std::vector<int> vec = {1, 2, 3};
for (auto it = vec.begin(); it != vec.end(); ++it) { /* … */ }
auto 在模板编程和迭代器遍历中尤其方便。注意 auto 会忽略引用和顶层 const,如果需要保持,要写成 auto& 或 const auto&。
1.2 decltype
有时想获得一个表达式的类型(但不计算表达式),用 decltype:
int a = 10;
decltype(a) b = 20; // b 是 int
decltype((a)) c = a; // 注意:双括号推导出 int&
decltype 常用于泛型编程中推导返回类型。
2. 统一的初始化与初始化列表
2.1 列表初始化(统一初始化)
C++11 推广了花括号初始化,基本所有类型都能用 {} 进行初始化,且它不允许窄化转换(更安全)。
int a{5};
double b{3.14};
std::vector<int> v{1, 2, 3, 4};
std::map<std::string, int> m{{"Alice", 25}, {"Bob", 30}};
// 防止窄化
// int x{3.14}; // 编译错误!double 到 int 窄化
2.2 std::initializer_list
让自定义容器也能支持列表初始化。在类中提供一个参数为 std::initializer_list<T> 的构造函数即可。
class MyVec {
public:
MyVec(std::initializer_list<int> list) {
for (auto x : list) std::cout << x << " ";
}
};
MyVec v = {1, 2, 3}; // 打印 1 2 3
3. 范围 for 循环(Range-based for loop)
告别传统下标或迭代器循环,遍历容器更简洁:
std::vector<int> nums = {1, 2, 3, 4};
for (int n : nums) {
std::cout << n << " ";
}
// 如需修改,使用引用
for (int& n : nums) {
n *= 2;
}
它对 C 风格数组、std::initializer_list、所有有 begin()/end() 的类型都有效。
4. 空指针 nullptr
以前用 NULL(本质是整数 0)可能会引起重载歧义。C++11 引入 nullptr,类型为 std::nullptr_t,可以隐式转换为任意指针类型,但不能转换为整数。
void func(int);
void func(char*);
func(nullptr); // 调用 char* 版本,而不会错误调用 int 版本
建议:从现在开始,用 nullptr 完全替代 NULL 或 0。
5. 面向对象增强:override、final、委托构造、默认/删除函数
5.1 override 和 final
在继承中,重写虚函数时加上 override 可让编译器检查签名是否正确。
class Base {
public:
virtual void foo() const;
};
class Derived : public Base {
public:
void foo() const override; // 正确重写
// void foo() override; // 编译错误:签名不匹配(缺少 const)
};
final 可以阻止虚函数被进一步重写,或者阻止类被继承:
class NoMoreDerived final { /* … */ };
class A { virtual void bar() final; };
5.2 委托构造函数
一个构造函数可以调用同类的另一个构造函数,避免重复代码。
class Widget {
public:
Widget() : Widget(0, 0) {} // 委托给下面的构造函数
Widget(int x, int y) : x_(x), y_(y) {}
private:
int x_, y_;
};
5.3 默认和删除函数
= default 显式要求编译器生成默认实现(如默认构造),= delete 则禁掉某个函数。
class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete; // 禁止拷贝构造
NonCopyable& operator=(const NonCopyable&) = delete; // 禁止拷贝赋值
};
6. 函数式编程:Lambda 表达式
Lambda 是一个可以捕获外部变量的匿名函数对象,极大地简化了 STL 算法中的自定义操作。
基本语法:
[捕获列表](参数列表) -> 返回类型 { 函数体 }
例子:
std::vector<int> v = {3, 1, 4, 1, 5};
// 降序排序
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
// 捕获外部变量
int threshold = 3;
auto it = std::find_if(v.begin(), v.end(),
[threshold](int x) { return x > threshold; });
常用捕获方式:
-
[] 不捕获
-
[=] 按值捕获全部
-
[&] 按引用捕获全部
-
[a, &b] 混合捕获
Lambda 是 C++11 里最受欢迎的特性之一,配合算法库特别好用。
7. 智能指针:unique_ptr、shared_ptr、weak_ptr
手动 new/delete 容易导致内存泄漏,智能指针利用 RAII 思想自动管理内存。
7.1 std::unique_ptr
独占所有权,不可拷贝,只能移动(move)。轻量级,开销极小。
std::unique_ptr<int> p1(new int(42));
auto p2 = std::make_unique<int>(100); // C++14 写法,C++11 中可自己封装
// auto p3 = p1; // 编译错误!不能拷贝
auto p3 = std::move(p1); // 转移所有权,p1 变为空
7.2 std::shared_ptr
共享所有权,内部有引用计数,当计数归零时自动释放资源。
std::shared_ptr<int> sp1 = std::make_shared<int>(200);
std::shared_ptr<int> sp2 = sp1; // 引用计数变为 2
7.3 std::weak_ptr
配合 shared_ptr 使用,可观察对象但不增加引用计数,解决循环引用问题。
std::weak_ptr<int> wp = sp1;
if (auto sp = wp.lock()) { // 尝试提升为 shared_ptr
// 使用 sp
}
强烈建议:现代 C++ 中尽量避免直接使用 new/delete,优先使用智能指针。
8. 右值引用与移动语义
8.1 左值与右值
-
左值:有名字、可取地址的表达式(变量、引用等)。
-
右值:临时对象、字面量、表达式结果等。
&& 表示右值引用,可以绑定到右值上,从而“偷”它的资源。
8.2 移动构造函数与移动赋值
允许容器或对象在拷贝时“转移”资源而不是深拷贝,极大提升效率。标准库容器(如 vector、string)都支持移动语义。
std::string s1 = "hello";
std::string s2 = std::move(s1); // 移动后 s1 不再使用(处于有效但未定义状态)
8.3 如何为自己的类加上移动语义
定义移动构造函数和移动赋值运算符:
class Buffer {
char* data;
size_t size;
public:
// 移动构造
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
// 移动赋值
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
};
加上 noexcept 至关重要,它能让标准容器在做扩容等操作时优先选择移动,从而提升性能。
9. constexpr 与编译期计算
constexpr 指明一个变量或函数的值或结果可以在编译期确定,从而用于模板参数、数组大小等场景,也可以提升运行时性能。
constexpr int square(int x) { return x * x; }
int arr[square(5)]; // 数组大小为 25,编译期计算
constexpr 函数有较多限制(如函数体必须只有一条 return 语句,C++11),但到了 C++14/17 逐步放宽。
10. 枚举类(强类型枚举)
传统 enum 会将枚举值暴露到外层作用域,且可以隐式转为 int。C++11 引入了 enum class:
enum class Color { Red, Green, Blue };
Color c = Color::Red; // 必须加作用域
// int n = c; // 编译错误,不能隐式转换
int n = static_cast<int>(c); // 显式转换
这样更安全,不会命名污染,也不会无意间算术运算。
11. 静态断言 static_assert
在编译期检查条件,如果不满足则产生编译错误并显示提示信息。比运行时的 assert 更早发现错误。
static_assert(sizeof(void*) == 8, "This code requires 64-bit platform");
template <typename T>
void process(T t) {
static_assert(std::is_integral<T>::value, "T must be integral type");
}
12. 可变参数模板(Variadic Templates)
模板现在可以接受任意数量和类型的参数,使得实现通用函数和类型更加灵活。
void print() {} // 递归终止函数
template <typename T, typename… Args>
void print(T first, Args… rest) {
std::cout << first << " ";
print(rest…);
}
print(1, 2.5, "hello"); // 输出 1 2.5 hello
标准库中 std::tuple、std::make_shared 等都依赖于可变参数模板。
13. 多线程支持
C++11 提供了标准化的线程库,位于 <thread>、<mutex>、<condition_variable>、<atomic>、<future> 中。
#include <thread>
#include <iostream>
void hello() {
std::cout << "Hello from thread\\n";
}
int main() {
std::thread t(hello);
t.join(); // 等待线程结束
return 0;
}
配合 lambda、std::async 等可以实现方便的异步编程。原子操作和互斥锁也不再依赖平台 API。
14. 其他有用的小特性
-
using 别名:比 typedef 更可读,支持模板别名。
cpp
template<typename T>
using Vec = std::vector<T, std::allocator<T>>; -
noexcept 说明符:标记函数不会抛出异常,帮助编译器优化。
-
= default / = delete:前面已述。
-
long long 类型:至少 64 位的整数。
-
begin()/end() 非成员函数:可以操作 C 数组。
15. 综合实例:结合多种特性
下面这个小程序展示了一些特性的混合使用:
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
int main() {
// 列表初始化 + auto
auto nums = {5, 2, 8, 1, 9};
// 智能指针 + vector
std::vector<std::shared_ptr<int>> ptrVec;
for (auto n : nums) {
ptrVec.push_back(std::make_shared<int>(n));
}
// lambda 表达式排序
std::sort(ptrVec.begin(), ptrVec.end(),
[](const auto& a, const auto& b) { return *a < *b; });
// 范围 for 输出
for (const auto& p : ptrVec) {
std::cout << *p << " ";
}
std::cout << std::endl;
// nullptr
std::shared_ptr<int> empty = nullptr;
std::cout << "empty is " << (empty ? "not null" : "null") << std::endl;
return 0;
}
16. 小结
C++11 的新特性远不止这些,但以上列举的是实际编码中使用频率最高、最能改变编程习惯的内容。建议:
-
循序渐进:先掌握 auto、范围 for、智能指针、lambda,再深入右值引用和模板。
-
改写旧代码:将以前的裸指针逐步替换为智能指针,将遍历改为范围 for,体验安全性和简洁性的提升。
-
关注异常安全:移动语义和智能指针帮你写出异常安全的代码。
-
利用编译期能力:善用 constexpr 和 static_assert。
C++11 是通往现代 C++ 的大门,掌握它就是掌握了更高效、更安全的编程范式。建议把以前写的部分代码用 C++11 新特性重构一下,感受一下那种轻爽的感觉!





