noexcept的作用
一、背景
在写 C++ 的时候,我们经常会看到有人在函数后面加上 noexcept。
刚开始接触时,大部分人只会模糊地知道:
"
noexcept告诉编译器这个函数不会抛异常。"
但它的作用并不仅仅是“承诺不抛异常”。在 STL 容器、移动语义等场景下,noexcept 直接影响代码的性能表现。
本文就从几个角度来聊聊 noexcept:
- 它的默认规则是什么?
- 它对性能有多大影响?
noexcept 的行为在不同类型的函数上有差异。
-
普通函数
- 默认倾向:可能抛出。
- 如果你没写
noexcept,编译器假设它可能会抛异常。 - 示例:
void func1(); // 等价于 void func1() noexcept(false);
-
特殊函数(构造/析构/移动操作等)
- 默认倾向:不会抛出。
- 编译器希望这些函数是
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,即使写了移动构造函数,也可能在扩容时退回到拷贝,浪费性能。