C++ 11 新特性

C++11 新特性

C++11 的定位

C++11(ISO/IEC 14882:2011)是”现代 C++”的起点。它带来的不是零散的新语法,而是三套改变了日常写法的能力:类型推导(auto/decltype)、所有权模型(智能指针、移动语义)、函数式风格(lambda、range-for)。C++14/17 都是它的增量完善,理解 C++11 是理解后续所有版本的地基。

C++11 特性地图:语言核心、所有权模型、标准库新增
图 1:C++11 特性地图——语言核心、所有权模型、标准库三个板块

类型推导:auto 与 decltype

auto 按”值”推导,会丢弃引用与顶层 const——要引用就显式写 auto&,要只读就写 const auto&。decltype 保留完整类型(含引用)。

#include <vector>

auto i = 42;                  // int
auto d = 3.14;                // double
auto s = "hello";             // const char*

std::vector<int> vec;
auto it = vec.begin();        // std::vector<int>::iterator

int x = 10;
decltype(x) y = 20;           // y 也是 int

auto 最常见的用途是迭代器和 lambda 变量——这类类型写出来又长又没信息量。函数返回类型则建议显式写,接口处可读性优先。

nullptr 与强类型枚举

void f(int);
void f(void*);

f(NULL);      // 问题:NULL 是 0,两个重载都能匹配 → 编译错误或误选 int 版
f(nullptr);   // 明确选中 void* 版本

enum class Color { Red, Green, Blue };
Color c = Color::Red;           // 必须带作用域
int n = static_cast<int>(c);    // 不隐式转 int,显式转换

nullptr 有独立类型 std::nullptr_t,可与任何指针比较,不会被重载决议误匹配到整数版本。枚举用 enum class 是默认选择:作用域限定、不隐式转 int,避免旧 enum 的全局命名污染。

智能指针:所有权显式化

#include <memory>

// unique_ptr:独占所有权,不可拷贝、可移动
std::unique_ptr<int> p1(new int(10));
auto p2 = std::move(p1);        // 所有权转移,p1 变为空

// shared_ptr:引用计数共享
std::shared_ptr<int> sp(new int(20));
auto sp2 = sp;                  // 计数 +1,最后一个释放时 delete

// weak_ptr:弱引用,不增加计数,防止循环引用
std::weak_ptr<int> wp = sp;
if (auto locked = wp.lock()) {  // 提权成功才安全使用
    *locked = 30;
}

裸 new/delete 只应出现在极少数底层代码里。接口之间统一约定:要么传引用,要么传智能指针,绝不裸指针进出——否则所有权语义混乱,悬空指针只能靠排查。

右值引用与移动语义

移动构造/移动赋值”偷走”源对象的资源(指针、句柄)而不是深拷贝,这是 vector 扩容、字符串拼接能保持高效的根本原因。

void process(int& x)  { /* 左值版本 */ }
void process(int&& x) { /* 右值版本 */ }

process(a);   // 左值 → 调 int& 版本
process(20);  // 右值 → 调 int&& 版本

class MyString {
    char* data;
public:
    // 移动构造:偷走源对象的资源,并把源对象置空
    MyString(MyString&& other) noexcept : data(other.data) {
        other.data = nullptr;   // 源对象析构时不会双重释放
    }
};

两个易错点:std::move 本身不移动任何东西,它只是把左值 cast 成右值引用,让重载决议选中移动版本;移动构造必须标 noexcept,否则 vector 扩容时移动会悄悄退化成拷贝(标准库要求强异常保证时只敢用拷贝),性能问题只能靠 profile 发现。

Lambda 表达式

int factor = 10;
auto f = [factor](int x) { return x * factor; };   // 按值捕获
auto g = [&](int x) { return x + factor; };        // 按引用捕获全部

std::sort(v.begin(), v.end(),
          [](int a, int b) { return a > b; });     // 匿名比较器

lambda 替代了函数对象(functor)的绝大部分用途:就地定义、就近使用、能捕获局部变量。mutable 允许修改按值捕获的副本;C++14 起参数可用 auto(泛型 lambda)。

constexpr 与编译期计算

constexpr int square(int x) { return x * x; }
int arr[square(5)];   // 编译期算出 25,可作为数组长度

注意 C++11 的 constexpr 函数体只能有一条 return,实用价值有限;循环、分支等完整能力在 C++14 才放开。

标准库扩充

  • std::thread / std::async:语言级线程与异步任务,配合 mutex / lock_guard;
  • <random>:mt19937 + 分布对象,替代劣质的 rand();
  • <chrono>:类型安全的时间点与时长;
  • std::tuple / std::array:定长多元组与定长数组;
  • unordered_map / unordered_set:哈希容器进标准库;
  • <regex>:正则表达式。

其他重要特性

  • override / final:override 让编译器检查”确实在重写基类虚函数”,final 禁止继续重写;
  • 统一初始化:{} 初始化所有场景;类内成员可直接初始化;
  • 委托构造、=default / =delete:显式控制特殊成员函数;
  • 可变参数模板:类型安全的变参,替代 C 的 va_list;
  • using 别名:替代 typedef,且支持模板别名;
  • static_assert:编译期断言;noexcept:异常规格;原始字符串:R"(...)" 免转义。

实战体会

auto 要用但别滥用:迭代器、lambda、复杂模板类型上它省一半打字,局部变量全用 auto 会让代码失去自文档能力。

移动语义是最容易被低估的特性:一个正确实现移动构造的类,在大容器场景下性能差一个数量级;但忘了加 noexcept,vector 扩容时移动会退化成拷贝,这个坑只能靠性能分析发现。

智能指针不要和裸指针混着传。所有跨模块接口统一传引用或智能指针,悬空指针问题基本绝迹。

滚动至顶部