一、语言核心特性

1、auto 类型推导

让编译器自动推断类型

auto x = 10;          // int
auto it = v.begin(); // 容器迭代器

2、nullptr

替代 NULL / 0,类型安全

int* p = nullptr;

3、 范围 for 循环(range-based for)

for (auto& x : vec) {
    cout << x << endl;
}

4.1右值引用

C++的右值引用(rvalue reference)是C++11引入的一个非常重要的特性,主要用来实现“移动语义”和“完美转发”,从而大幅提升程序的性能,尤其是涉及资源管理(比如动态内存、文件句柄等)

什么是右值引用

  • 左值(lvalue):有名字,可以取地址的对象。比如变量名就是左值。
  • 右值(rvalue):没有名字,通常是临时对象或字面值,比如42x + y的结果、函数返回的临时对象等。

在传统C++中,函数参数通常是通过值传递(拷贝)或引用传递(左值引用)来接收,但拷贝性能开销大。引入右值引用后,可以对临时对象进行“移动”,避免不必要的拷贝。

右值引用的语法

T&& x;  // 右值引用

例如:

int&& a = 5;  // 5是右值,a是右值引用,绑定临时对象5

4.2 移动语义

移动语义允许你“窃取”临时对象的资源,而不是拷贝。常见场景是自定义类的拷贝构造函数和移动构造函数。

#include <iostream>
#include <cstring>

class MyString {
char* data;
public:
// 构造函数
MyString(const char* str = "") {
    data = new char[strlen(str) + 1];
    strcpy(data, str);
    std::cout << "Constructed: " << data << std::endl;
}

// 拷贝构造函数
MyString(const MyString& other) {
    data = new char[strlen(other.data) + 1];
    strcpy(data, other.data);
    std::cout << "Copy Constructed: " << data << std::endl;
}

// 移动构造函数
MyString(MyString&& other) noexcept {
    data = other.data;    // 直接“偷”资源指针
    other.data = nullptr; // 防止析构时释放资源
    std::cout << "Move Constructed" << std::endl;
}

// 析构函数
~MyString() {
    if(data) {
        std::cout << "Destructing: " << data << std::endl;
        delete[] data;
    } else {
        std::cout << "Destructing: null" << std::endl;
    }
}
};

MyString createString() {
    MyString temp("Temporary");
    return temp;  // 这里会调用移动构造
}

int main() {
    MyString s1 = createString(); // 移动构造
    //MyString s2 = s1;             // 拷贝构造
    return 0;
}

场景1

MyString createString() {
    MyString temp("Temporary");
    return temp;  // 这里预期调用移动构造函数
}

int main() {
    MyString s1 = createString(); // 这里期待调用移动构造
    // MyString s2 = s1;  // 注释掉了
}

为什么没有看到“Move Constructed”?
  1. 编译器优化(NRVO,返回值优化)
    C++编译器通常会启用一种叫做 返回值优化(RVO)命名返回值优化(NRVO) 的优化技术。
    这意味着:
  • createString()里创建的局部对象temp,编译器会直接在s1的内存位置上构造,而不是先构造temp再移动构造s1
  • 这样就避免了额外的移动构造或拷贝构造,节省性能。

换句话说,temps1其实是同一个对象,没有调用移动构造函数。


你看到的输出
Constructed: Temporary
Destructing: Temporary
  • “Constructed”是构造temp(其实直接构造到s1空间了)
  • “Destructing”是程序结束时析构s1,因为temps1是同一个对象
  • 没有“Move Constructed”,说明移动构造被省略了

场景2

MyString createString() {
    MyString temp("Temporary");
    return std::move(temp);  // 通过std::move强制转换成右值
}

这样写后,移动构造会被调用(一般不会被优化掉),控制台会输出“Move Constructed”。

4.3完美转发

完美转发(Perfect Forwarding)和右值引用密切相关,主要用来让函数模板把参数“原封不动”地转发给另一个函数,保持参数的值类别(左值还是右值)不变。

为什么需要完美转发?

假设你写了一个模板函数,想调用另一个函数并传递参数,但不知道传进来的参数是左值还是右值。普通的转发可能会导致参数丢失右值性质,从而调用不到对应的移动构造或者移动赋值等优化。


完美转发的关键是:

  • 使用模板类型参数T&&(这时T&&不是右值引用,而是转发引用,或称为万能引用 universal reference)
  • 使用std::forward<T>(param)完美转发参数

demo

#include <iostream>
#include <utility>

// 普通函数,分别接收左值引用和右值引用
void func(int& x) {
    std::cout << "func called with lvalue: " << x << std::endl;
}

void func(int&& x) {
    std::cout << "func called with rvalue: " << x << std::endl;
}

// 转发函数模板
template<typename T>
void wrapper(T&& arg) {
    // 完美转发给 func,保留左值/右值性质
    func(std::forward<T>(arg));
}

int main() {
    int a = 10;
    wrapper(a);        // 传入左值,func(int&)被调用
    wrapper(20);       // 传入右值,func(int&&)被调用
}

代码解释:

  • T&& arg 是转发引用(万能引用),可以绑定左值和右值
  • std::forward<T>(arg) 保证了传进去的参数如果是左值就传左值,右值就传右值
  • 这样func就能根据参数的值类别正确重载调用

总结

  • 右值引用用于移动语义,绑定右值,避免拷贝,提高性能
  • **转发引用(万能引用)**是模板中T&&参数,能绑定任意值类别
  • 完美转发利用std::forward让参数“完美地”传递给下层函数,保持原始的左值/右值属性

为什么很多开发者没用过完美转发?

  • 业务代码层面多是具体实现,不涉及通用模板库
    许多日常业务开发里写的代码,比如业务逻辑、数据处理、UI交互等,更多的是写具体功能,不需要写高度通用和模板化的转发代码。
  • 完美转发主要用于编写泛型库、框架或工具
    例如标准库中的std::forwardstd::make_uniquestd::bindstd::thread启动函数包装等,都需要完美转发来做到参数的“零开销转发”。这类代码是框架层或库层的事情。
  • 使用不当风险较高
    完美转发如果用错(比如忘记用std::forward而用std::move或者直接传参),会破坏参数的值类别,导致性能下降或语义错误,不太适合初学者或者快速开发场景。

典型业务场景示例

  1. 泛型工厂函数
    你想写个createObject函数模板,不论传入构造函数的参数是左值还是右值,都原封不动传递给对象构造函数。
template<typename T, typename... Args>
T* createObject(Args&&... args) {
    return new T(std::forward<Args>(args)...);
}
  1. 事件回调、信号槽系统
    当你写一个事件调度系统,需要将回调函数和参数以泛型方式传递,完美转发保证参数的高效传递和调用。
  2. 包装器、代理函数
    写函数包装器(比如装饰器、日志、计时器)时,把参数完整地传给被包装函数,而不改变参数特性。
场景总结
  1. 完美转发特别适合写那些需要“透传参数”且不想损失效率和语义的通用模板代码。
  2. 如果你是业务开发者,可以暂时不用太纠结完美转发,但如果想写库、框架,或者追求极致性能和泛型代码,那么学会完美转发非常重要。

5、lambda表达式

auto f = [](int a, int b) {
    return a + b;
};

更详细的可以参考我之前写的文章《C++的lambda表达式》:https://blog.csdn.net/weixin_63787588/article/details/148208016

6、constexpr

能在编译期算的,就不要拖到运行期

一、场景 1:编译期常量(替代 #define / const

❌ 旧写法
#define N 100
✅ C++11 推荐
constexpr int N = 100;

int arr[N];   // 编译期确定
🔑 关键点
  • constexpr 变量是真正的编译期常量
  • const 更强(const 不一定是编译期)

二、场景 2:constexpr 函数(编译期计算)

Demo:编译期平方
constexpr int square(int x) {
    return x * x;
}

int arr[square(4)];  // OK,数组大小 = 16
对比普通函数
int square2(int x) {
    return x * x;
}

// int arr[square2(4)]; // ❌ 编译错误
🔑 关键点
  • 参数是编译期常量 → 编译期执行
  • 参数是运行期值 → 退化成普通函数

三、场景 3:用于 static_assert(非常常见)

Demo:编译期校验
constexpr int BufferSize = 1024;

static_assert(BufferSize % 2 == 0, "BufferSize must be even");
🔑 关键点
  • static_assert只能用编译期表达式
  • constexpr 是黄金搭档

四、场景 4:constexpr 构造函数(C++11)

Demo:编译期对象
struct Point {
    int x, y;
    constexpr Point(int a, int b) : x(a), y(b) {}
};

constexpr Point p(1, 2);
static_assert(p.x == 1, "x must be 1");

🔑 关键点

  • 构造函数体必须很简单(C++11 要求更严格)
  • 成员必须是 字面值类型

五、场景 5:枚举 / 配置 / 编译期策略

Demo:编译期参数
constexpr int MaxConnections(bool debug) {
    return debug ? 10 : 100;
}

constexpr int maxConn = MaxConnections(false);

六、场景 6:模板元编程(高阶)

Demo:编译期斐波那契
constexpr int fib(int n) {
    return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}

static_assert(fib(10) == 55, "fib error");

七、场景 7:替代 magic number(工程实践)

❌ 不推荐
if (size > 4096) { }
✅ 推荐
constexpr size_t MaxPacketSize = 4096;

if (size > MaxPacketSize) { }

八、constexpr vs const(面试必问)

对比项constconstexpr
是否编译期不一定一定
可用于数组大小
可用于模板参数
语义只读编译期常量

九、常见坑 ⚠️

❌ constexpr 函数不一定在编译期执行
int x;
cin >> x;

constexpr int y = square(x); // ❌
❌ C++11 限制多
constexpr int f(int x) {
    int y = x + 1;   // ❌ C++11 不允许局部变量
    return y;
}

(C++14 之后放宽)


十、一句话总结(面试用)

constexpr 用于 声明编译期常量或可在编译期执行的函数,常见于数组大小、模板参数、static_assert、配置值和性能敏感代码中。

7、decltype

auto 用“值”推类型,decltype 用“表达式”推类型。

一、最核心区别(一定要记)

对比点autodecltype
推导依据初始化的值表达式本身
会不会丢引用不会
会不会丢 cv不会
是否需要初始化必须不需要
常用于变量声明精确类型获取

二、场景 1:最基本用法

auto:看右值
int x = 10;
auto a = x;   // int
decltype:看表达式
int x = 10;
decltype(x) b = x; // int

看不出区别?继续👇


三、场景 2:引用和 const(关键区别 ⭐)

int x = 10;
int& rx = x;
const int cx = x;
auto 会“脱衣服”
auto a1 = rx;   // int
auto a2 = cx;   // int
decltype 完全保留
decltype(rx) d1 = x; // int&
decltype(cx) d2 = x; // const int

👉 auto 默认按值推导,decltype 精确保留类型


四、场景 3:表达式规则(面试高频)

decltype 的核心规则
decltype(expr)
  • expr变量名 → 得到声明类型
  • expr表达式 → 得到表达式类型(可能是引用)
Demo
int x = 10;

decltype(x) a = x;     // int
decltype((x)) b = x;   // int&   ← 注意括号!

📌 双括号 = 左值表达式 = 引用


五、场景 4:函数返回类型(经典用法 ⭐)

❌ auto 做不到
template<typename T, typename U>
??? add(T a, U b);
✅ decltype
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
    return a + b;
}

(C++14 才能直接 auto add(...)


六、场景 5:STL / 泛型编程(非常常见)

获取容器元素类型
std::vector<int> v;

decltype(v)::value_type x; // int
推导迭代器解引用类型
auto it = v.begin();
decltype(*it) y = *it; // int&

七、场景 6:配合 auto(最佳实践)

auto it = v.begin();
decltype(it) it2 = v.end();

👉 auto 声明,decltype 复用


八、auto + decltype 实战对比

❌ 错误写法

auto get() { return x; } // C++11 不支持

✅ C++11 正确写法

auto get() -> decltype(x) {
    return x;
}

九、面试总结模板(直接背)

auto 根据初始化表达式推导类型,会忽略引用和 cv;
decltype 根据表达式本身推导类型,能精确保留引用和 const,
常用于泛型编程、返回类型推导和复杂类型获取。


十、什么时候用谁?

  • **用 ****auto**
    👉 简化变量声明(迭代器、lambda、复杂模板)
  • **用 ****decltype**
    👉 精确类型、模板返回值、引用语义不能丢时

8、using 类型别名

替代 typedef(更清晰):

using int64 = long long;

9、static_assert

编译期断言:

static_assert(sizeof(int) == 4, "int must be 4 bytes");

10、explicit 支持多参数构造

explicit 是干什么的?

一句话定义(面试版):

**explicit**** 用来禁止构造函数或转换函数发生“隐式类型转换”。**

它的目的只有一个:
👉 防止“看起来合法,其实有坑”的自动类型转换


最经典的隐式转换问题(不加 explicit)

问题代码(⚠️ 很危险)
class Foo {
public:
    Foo(int x) {
        cout << "Foo(int)" << endl;
    }
};

void func(Foo f) {
}

int main() {
    func(10);   // ❗️这里发生了什么?
}
实际发生的事
10  → Foo(10) → func(Foo)

👉 编译器悄悄帮你做了构造函数调用

输出:

Foo(int)
问题在哪?
  • func 明明要的是 Foo
  • 却能传一个 int
  • 调用者根本意识不到发生了对象构造

explicit 如何解决这个问题?

正确写法(加 explicit)
class Foo {
public:
    explicit Foo(int x) {
        cout << "Foo(int)" << endl;
    }
};

void func(Foo f) {
}

int main() {
    // func(10);    // ❌ 编译错误
    func(Foo(10)); // ✅ 必须显式构造
}
结果
  • ❌ 隐式转换被禁止
  • ✅ 调用者必须明确表达意图

explicit 的本质规则

哪些构造函数会触发隐式转换?

单参数构造函数

Foo(int x);       // 会触发
Foo(int x, int y); // 不会

explicit 作用的真实规则

隐式允许的情况(没 explicit)

Foo f = 10;   // int → Foo
func(10);     // int → Foo
explicit 禁止的情况
explicit Foo(int x);

Foo f = 10;   // ❌
func(10);     // ❌
Foo f(10);    // ✅

explicit + 多参数构造(C++11 新增)

C++11 之前(explicit 只能修饰单参)
class Foo {
public:
    explicit Foo(int x, int y); // ❌ C++98 不允许
};
C++11 之后(合法)
class Foo {
public:
    explicit Foo(int x, int y) {
        cout << "Foo(int, int)" << endl;
    }
};

作用场景

Foo f1(1, 2);        // ✅
Foo f2 = {1, 2};    // ❌ 禁止隐式列表初始化

explicit 与 initializer_list 的经典坑

没有 explicit(容易踩雷)
class Foo {
public:
    Foo(int x) { }
};

Foo f = {10};   // 隐式转换
加 explicit
class Foo {
public:
    explicit Foo(int x) { }
};

Foo f = {10};   // ❌ 编译错误
Foo f(10);      // ✅

explicit 还能修饰什么?(C++11)

转换运算符(非常重要)
class Foo {
public:
    explicit operator bool() const {
        return true;
    }
};
效果对比
Foo f;

if (f) { }        // ✅ 允许(上下文转换)
bool b = f;       // ❌ 禁止

👉 防止对象被随意当成 bool/int 使用


explicit 的工程级使用建议

什么时候一定要加?
  • 单参数构造函数
  • 表示“语义转换”的构造函数
  • 配置类 / 资源类 / 句柄类
什么时候可以不加?
  • 明确是“数值包装”的类型(如 std::chrono 内部)
  • 你非常确定隐式转换是安全且合理的

二、面向对象 & 语法增强

11、override / final

override

重写

class Base {
    virtual void foo() = 0;
};

class Derived : public Base {
    void foo() override;
};

一、final 是干什么的?

final 用来禁止继承或禁止虚函数被重写,主要解决两个问题:

  1. 防止类被继承
  2. 防止虚函数被继续 override

一句话:

final = “到此为止,不准再改”


二、final 修饰类:禁止被继承

经典 Demo

class Base final {
};

class Derived : public Base {  // ❌ 编译错误
};

作用

  • 明确设计意图:这个类不允许作为基类
  • 常见于:
    • 工具类
    • RAII 类
    • 框架内部实现类

三、final 修饰虚函数:禁止子类重写

经典 Demo

class Base {
public:
    virtual void foo() final {
    }
};

class Derived : public Base {
public:
    void foo() override {   // ❌ 编译错误
    }
};

作用

  • 防止子类破坏基类行为
  • 保证关键逻辑不可被修改

四、finaloverride 的关系(高频面试点)

正确用法(强烈推荐)

class Derived : public Base {
public:
    void foo() override final;
};

含义

  • override我在重写父类虚函数
  • final你们不能再重写我

👉 常见在 中间层类


五、为什么需要 final?(真实问题)

没有 final 的风险

class Base {
public:
    virtual void process() {
        step1();
        step2();
    }
};

子类可能这样:

class BadDerived : public Base {
public:
    void process() override {   // 破坏流程
        step2();
        step1();
    }
};

final 保护

class Base {
public:
    virtual void process() final {
        step1();
        step2();
    }
};

六、final 带来的额外好处:性能优化

编译器可做的优化

  • final 虚函数 → 可去虚表
  • 调用可被 内联
  • 减少间接调用开销

👉 在性能敏感代码(游戏 / 引擎 / 高频调用)很常见


七、final vs const(别混)

关键字作用对象含义
final类 / 虚函数禁止继承 / 重写
const成员函数 / 变量不允许修改

八、final 的常见误用 ⚠️

❌ 用在非虚函数上

void foo() final; // ❌ 没意义(C++直接禁止)

❌ 滥用

class EverythingFinal final { };

会导致:

  • 扩展性差
  • 测试 / mock 困难

九、什么时候该用 final?(工程经验)

✅ 推荐使用:

  • 框架底层类
  • 不希望被继承的工具类
  • 关键算法函数
  • 性能敏感的虚函数

❌ 不推荐:

  • 业务层可扩展类
  • 明确需要多态扩展的接口

十、面试标准回答(背这个)

final 用于禁止类被继承或禁止虚函数被重写,可以明确设计意图、防止误用,同时帮助编译器做性能优化,常与 override 配合使用。

12、默认/删除函数

Foo() = default;
Foo(const Foo&) = delete;

13、委托构造函数

Foo(int x) : Foo(x, 0) {}

14、enum class(强类型枚举)

enum class Color { Red, Green, Blue };

三、STL 和库增强

15、智能指针

  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr

可以参考之前写的文章:https://blog.csdn.net/weixin_63787588/article/details/134458428?spm=1001.2014.3001.5501


16、新容器

  • std::array
  • std::unordered_map
  • std::unordered_set

17、std::function / std::bind

std::function<int(int)> f;

18、正则表达式

std::regex r("\\d+");

19、时间库 <chrono>

std::chrono::steady_clock::now();

20、线程库

std::thread t(func);
std::mutex m;
std::lock_guard<std::mutex> lock(m);

四、模板相关增强

21、变参模板

template<typename... Args>
void func(Args... args);

22、std::initializer_list

vector<int> v = {1, 2, 3};

23、SFINAE + decltype 改进

一、先快速理解:SFINAE 是在解决什么问题?

SFINAE(Substitution Failure Is Not An Error) 的核心作用是:

在模板实例化时,如果某个类型推导失败,不报错,而是“自动忽略该模板”

这使得我们可以:

  • 根据 类型是否支持某个操作
  • 有 / 没有某个成员函数
  • 表达式是否合法

选择不同的模板版本


二、为什么要引入 decltype

2.1 传统 SFINAE 的问题

老写法通常是这样(C++11 之前或早期):

template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type
func(T t) {}

问题:

  • 只能判断「类型特性」(如是不是整数)
  • 无法判断「某个表达式是否成立」
  • 写法冗长,可读性差

2.2 decltype 的优势

decltype(expr) 能做到一件关键的事:

把“表达式是否能编译”变成一个类型推导问题

如果 expr不合法

  • decltype(expr) 推导失败
  • 触发 SFINAE
  • 模板被自动排除

这正是 SFINAE 最想要的能力。


三、最经典的用法:判断表达式是否存在

3.1 基本模式(核心模板)

template<typename T>
auto func(T t) -> decltype(t.size(), void())
{
    // 只有 T 有 size() 才能进来
}
发生了什么?
  • t.size() 只是用于检测是否合法
  • , void() 确保返回类型是 void
  • 如果 T 没有 size() → 推导失败 → SFINAE 生效

3.2 配合兜底版本(推荐写法)

template<typename T>
auto func(T t) -> decltype(t.size(), void())
{
    std::cout << "has size(): " << t.size() << "\n";
}

template<typename T>
void func(T)
{
    std::cout << "no size()\n";
}
使用效果
func(std::vector<int>{1,2,3}); // has size()
func(10);                      // no size()

四、decltype + SFINAE 的改进写法(比 enable_if 好)

4.1 利用返回类型推导(C++14 起)

template<typename T>
auto func(T t)
{
    if constexpr (requires { t.size(); })
    {
        std::cout << t.size();
    }
}

但如果不用 requires(C++20 之前)decltype 是主力。


4.2 常见改进套路:检测成员函数

template<typename T>
auto print(const T& t) -> decltype(t.toString(), void())
{
    std::cout << t.toString();
}

template<typename T>
void print(const T&)
{
    std::cout << "no toString()\n";
}

适合场景:

  • 日志系统
  • 通用调试输出
  • 框架代码(适配用户类型)

五、结合 std::declval(最常用组合)

5.1 为什么要 declval

有些类型:

  • 不可构造
  • 构造代价很高

std::declval<T>() 可以:

  • 在不创建对象的情况下
  • 假装有一个 T

5.2 标准写法(工业级)

template<typename T>
auto func(int) -> decltype(std::declval<T>().size(), void())
{
    std::cout << "has size\n";
}

template<typename T>
void func(...)
{
    std::cout << "no size\n";
}
这是一个完整的 SFINAE 分发技巧
  • int 版本优先
  • 推导失败才会落到 ...

六、典型使用场景总结

6.1 通用库 / 框架代码

  • STL 风格算法
  • 序列化 / 反序列化
  • 打印、日志、调试工具

6.2 类型适配(鸭子类型)

// 只关心“能不能这样用”,不关心继承
obj.size();
obj.begin();
obj.end();

6.3 编译期能力检测

  • 判断是否支持 operator<<
  • 判断是否支持 push_back
  • 判断是否可比较

七、什么时候不该用 SFINAE + decltype?

7.1 能用 Concepts / requires 时

如果你用的是 C++20+

template<typename T>
requires requires(T t) { t.size(); }
void func(T t) {}

👉 更清晰、更安全


八、一句话总结

**SFINAE + decltype 的核心价值是:
**把“表达式是否合法”变成模板选择条件

它最适合:

  • 泛型库
  • 类型无关代码
  • 编译期能力检测

五、其他实用特性

24、原始字符串

std::string s = R"(C:\path\file.txt)";

25、noexcept

一、noexcept 是干什么的?(一句话版)

**noexcept**** 用来告诉编译器:这个函数“保证不会抛异常”**

它影响三件事:

  1. **程序是否直接 ****std::terminate**
  2. 标准库是否选择这个函数
  3. 编译器能不能做更激进的优化

二、最基本用法

2.1 标注函数不会抛异常

void f() noexcept
{
    // 不允许抛异常
}

如果 f() 内部真的抛了异常:

throw std::runtime_error("err"); // ❌

👉 程序立刻调用 **std::terminate()**,不会被 catch


2.2 对比不写 noexcept

void g()
{
    throw 1;
}

try {
    g();
} catch (...) {
    // 能捕获
}

三、条件式 noexcept(重点)

3.1 语法

noexcept(表达式)

**如果表达式在编译期为 true → noexcept
**否则 → 可能抛异常


3.2 典型写法(模板必学)

template<typename T>
void func(T& t) noexcept(noexcept(t.swap(t)))
{
    t.swap(t);
}

含义:

  • 如果 T::swapnoexcept
  • func 也是 noexcept
  • 否则就不是

四、noexcept 是如何参与重载选择的?

4.1 noexcept 是函数类型的一部分(C++17)
void f() noexcept;
void f();          // 不同类型

👉 但 不能只靠 noexcept 重载

void f() noexcept {}
void f() {} // ❌ 重定义

4.2 标准库为什么在乎 noexcept

std::vector 移动构造
std::vector<T> v2 = std::move(v1);

规则是:

**只有当 **T** 的移动构造是 **noexcept**
**vector 才会用移动,否则用拷贝

struct A {
    A(A&&);            // 可能抛异常
};

struct B {
    B(B&&) noexcept;   // 不抛异常
};

👉 vector<B> 扩容时更快、更安全


五、noexcept 运算符(容易混)

5.1 这是一个编译期运算符

noexcept(expr)

返回:

  • true:expr 不会抛异常
  • false:可能抛异常
static_assert(noexcept(42));
static_assert(!noexcept(throw 1)); // 永远 false

5.2 和函数声明里的 noexcept 不同

void f() noexcept;        // 承诺
bool b = noexcept(f());  // 查询

六、什么时候必须写 noexcept

6.1 移动构造 / 移动赋值(强烈建议)

struct X {
    X(X&&) noexcept;
    X& operator=(X&&) noexcept;
};

否则:

  • vector<X> 性能下降
  • 某些容器直接退化成拷贝

6.2 析构函数(隐式 noexcept)

~X();        // 默认是 noexcept(true)

不能让析构抛异常

~X() noexcept(false); // ❌ 非常危险

七、什么时候不该写 noexcept

7.1 你无法保证不抛异常时

void log()
{
    std::string s;
    s += "xxx"; // 可能分配失败
}

❌ 不要乱加 noexcept


7.2 接口层 / 业务层

  • 框架底层:多用 noexcept
  • 业务逻辑:让异常自然传播更好

八、noexcept vs throw()(旧)

void f() throw();  // C++11 以前
  • 已废弃
  • 行为接近 noexcept(true)
  • 不要再用

九、一句话总结

**noexcept**** 是“异常契约” + “优化提示”**

记住三点就够:

  1. 承诺不抛 → 抛了就 terminate
  2. 模板里要用条件 noexcept
  3. 移动构造一定要 noexcept

26、对齐控制

alignas(16) int x;
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐