c++11语法-noexcept

68 阅读2分钟

noexcept的作用

一、背景

在写 C++ 的时候,我们经常会看到有人在函数后面加上 noexcept
刚开始接触时,大部分人只会模糊地知道:

"noexcept 告诉编译器这个函数不会抛异常。"

但它的作用并不仅仅是“承诺不抛异常”。在 STL 容器、移动语义等场景下,noexcept 直接影响代码的性能表现。

本文就从几个角度来聊聊 noexcept

  • 它的默认规则是什么?
  • 它对性能有多大影响?

noexcept 的行为在不同类型的函数上有差异。

  1. 普通函数

    • 默认倾向:可能抛出
    • 如果你没写 noexcept,编译器假设它可能会抛异常。
    • 示例:
      void func1(); 
      // 等价于
      void func1() noexcept(false);
      
  2. 特殊函数(构造/析构/移动操作等)

    • 默认倾向:不会抛出
    • 编译器希望这些函数是 noexcept(true),因为这对性能优化(尤其是移动语义)非常关键。
    • 例外情况:如果它内部调用了一个“可能抛异常”的操作(比如成员变量的析构函数不是 noexcept),那编译器就会把它推翻为 noexcept(false)

总结一句话:普通函数默认可能抛异常,特殊函数默认不抛,除非被连累。

三、对性能的影响

以vector的扩容来看待,vecotr在扩容的时候,会使用通过std::move来使用移动构造函数避免进行拷贝构造。

2.1 案例分析

前提知识:拷贝构造函数仅当类没有显式声明拷贝构造函数时才自动生成,并且如果用户声明了移动操作,拷贝构造就是delete。拷贝赋值运算符仅当类没有显式声明拷贝赋值运算符时才自动生成,并且如果用户声明了移动操作,拷贝赋值运算符就是delete。当用户声明了析构函数,拷贝操作的自动生成已被废弃。 Effective Modern C++ 17

#include <iostream>
#include <vector>
#include <string>
#include <chrono>

using namespace std;

const int DATA_SIZE = 10000;

struct DataWithNoexcept {
    std::vector<int> values;

    DataWithNoexcept() : values(DATA_SIZE) {}

    DataWithNoexcept(DataWithNoexcept&& other) noexcept
        : values(std::move(other.values)) {
    }
    DataWithNoexcept(DataWithNoexcept& other)
        : values(other.values) {
    }

};

struct DataWithoutNoexcept {
    std::vector<int> values;

    DataWithoutNoexcept() : values(DATA_SIZE) {}

    DataWithoutNoexcept(DataWithoutNoexcept&& other)
        : values(std::move(other.values)) {
    }

    DataWithoutNoexcept(const DataWithoutNoexcept& other)
        : values(other.values) {
    }
};

template<typename T>
double testVectorMove(int N) {
    vector<T> vec;
    
    auto start = chrono::high_resolution_clock::now();
    for (int i = 0; i < N; ++i) {
        vec.push_back(T{}); // 使用默认构造函数创建包含大量数据的对象
    }
    auto end = chrono::high_resolution_clock::now();
    chrono::duration<double> diff = end - start;
    return diff.count();
}

int main() {
    const int N = 200000; 

    double t1 = testVectorMove<DataWithNoexcept>(N);
    cout << "With noexcept:    " << t1 << " seconds" << endl;

    double t2 = testVectorMove<DataWithoutNoexcept>(N);
    cout << "Without noexcept: " << t2 << " seconds" << endl;

    return 0;
}
With noexcept比:    4.8716 seconds
Without noexcept: 7.45851 seconds

会发现:带有noexcept的能快不少

2.2 分析

  • STL 容器在内部 只有当移动构造函数 noexcept 才会优先移动而不是拷贝
  • 对于用户自定义类型,如果没有加 noexcept,即使写了移动构造函数,也可能在扩容时退回到拷贝,浪费性能。