函数说明符是否noexcept
旨在提高性能,因为生成的对象中可能没有记录异常的代码,因此应尽可能将其添加到函数声明和定义中?我首先想到的是可调用对象的包装器noexcept
,尽管检查表达式可能会使源代码“膨胀”,但它可能会产生一些影响。这值得吗?
3 回答
从理论上讲,noexcept
会提高性能。但另一方面,它也可能会导致一些问题。
在大多数情况下,不应指定它,因为专业人士太少而无法考虑,并且可能会使您的代码升级变得痛苦。这篇由 Andrzej 撰写的帖子详细介绍了原因。
如果它太长,只需接受我从中得出的这些建议:
noexcept
用if 注释函数- 他们已经注释了
throw()
, - 或者他们是很好的候选人(在帖子中列出)并且永远不会确定,
- 或者它们是移动构造函数,移动赋值,其
noexcept
注释不能被编译器正确推断,它们的实例应该被放入一些 STL 容器中。
- 他们已经注释了
- 不要用
noexcept
if 注释函数- 你真的很担心性能下降,
- 或关于打电话的风险
std::terminate
, - 或者您只是不确定新功能,
- 或者你怀疑你是否应该做你的功能
noexcept
。
顶级编译器生成已经优化的代码,就像不能抛出的代码一样,然后发生异常的情况由异常处理机制通过查看有关功能。不过,我认为在已知不需要时省略它对代码大小有一些好处。
在某些情况下, nothrow 规范确实允许进行一些特定的优化:
int main() {
int i = 0;
try {
++i;
thing_that_cannot_throw();
++i;
thing_that_can_throw();
++i;
} catch (...) {}
std::cout << i << "\n";
}
这里第二个 ++i 理论上可以在调用之前重新排序thing_that_cannot_throw
(并且i
刚刚初始化为2
)。但是,是否在实践中是另一回事,因为保证调试器中或函数调用上方堆栈中变量状态的实现会希望在该调用期间i
具有值1
,即使它是不可观察的局部变量通过任何标准方式。
我怀疑 nothrow 保证对程序员比对编译器更有价值。如果您正在编写提供强异常保证的代码,那么您通常会执行某些关键操作,您需要提供 nothrow 保证(交换、移动和析构函数是常见的候选者)。
我偶然发现了一个“真实世界”的例子,其中 noexcept 有所作为。我想在这里分享它,因为它可能有助于其他人形成意见。
首先是一点背景知识:标准库容器试图做到“异常安全”。这意味着在引发(并捕获)异常后,它们会为您提供有关容器状态的某些保证。一个很好的例子是 std::vector::emplace_back。如果由于某种原因插入失败, emplace_back 保证向量看起来没有改变。请参阅 上的cppreferenceemplace_back
。然而,当向量需要重新定位以响应 emplace 时,这会变得很有趣。重新定位预先存在的向量项的(希望)最快的方法是将move
它们转移到新的扩大缓冲区。不幸的是,move
-construction 可能引发异常,所以如果值类型的move
-ctor 不是异常安全的,emplace_back
需要求助于复制操作。但是,由于可以在编译时探测一个类型的move-noexept'nessstd::vector
,如果证明是合法的,那么仍然会采用更快的方法。
我将以下谷歌基准放在一起在本地进行测量:
#include "benchmark/benchmark.h"
#include <vector>
// This type really benefits from being moved instead of being copied
struct SlowCopy {
SlowCopy(const size_t theSize) {
for (int i = 0; i < theSize; ++i)
itsData.emplace_back(i);
}
SlowCopy(const SlowCopy &) = default;
SlowCopy(SlowCopy &&) noexcept = default;
std::vector<int> itsData;
};
// The template parameter specifies whether the move constructor is noexcept or not
template<bool YesNo>
struct MovableNoexcept {
MovableNoexcept(const size_t theSize) : itsData{theSize} {}
MovableNoexcept(const MovableNoexcept &) = default;
MovableNoexcept(MovableNoexcept &&) noexcept(YesNo) = default;
MovableNoexcept& operator=(const MovableNoexcept &) = default;
MovableNoexcept& operator=(MovableNoexcept &&) noexcept(false) = default;
SlowCopy itsData;
};
// This benchmark takes 2 arguments:
// 1. How many items do we push into a vector
// 2. How big are the items that are in the vector
template<bool IsNoexcept>
static void BM_MoveRelocateNoexcept(benchmark::State& state) {
std::vector<MovableNoexcept<IsNoexcept>> aExcepts;
for (auto _ : state) {
for (int i = 0; i < state.range(0); ++i)
aExcepts.emplace_back(state.range(1));
benchmark::ClobberMemory();
}
}
// Test 1k elements @ 64*sizeof(int) kb
BENCHMARK_TEMPLATE(BM_MoveRelocateNoexcept, false)->Args({1000, 1 << 16})->Repetitions(20);
BENCHMARK_TEMPLATE(BM_MoveRelocateNoexcept, true)->Args({1000, 1 << 16})->Repetitions(20);
// Test 100 elements @ 512*sizeof(int) kb
BENCHMARK_TEMPLATE(BM_MoveRelocateNoexcept, false)->Args({100, 1 << 19})->Repetitions(20);
BENCHMARK_TEMPLATE(BM_MoveRelocateNoexcept, true)->Args({100, 1 << 19})->Repetitions(20);
// Run the benchmark
BENCHMARK_MAIN();
在我的本地系统上,我在运行基准测试时测量了以下结果:
Running ./noexcept_bench
Run on (8 X 4400 MHz CPU s)
CPU Caches:
L1 Data 32 KiB (x4)
L1 Instruction 32 KiB (x4)
L2 Unified 256 KiB (x4)
L3 Unified 8192 KiB (x1)
Load Average: 0.58, 0.70, 0.69
------------------------------------------------------------------------------------------------------
Benchmark Time CPU Iterations
------------------------------------------------------------------------------------------------------
BM_MoveRelocateNoexcept<false>/1000/65536/repeats:20_mean 157793886 ns 157556651 ns 20
BM_MoveRelocateNoexcept<false>/1000/65536/repeats:20_median 157752118 ns 157511285 ns 20
BM_MoveRelocateNoexcept<false>/1000/65536/repeats:20_stddev 294024 ns 292420 ns 20
BM_MoveRelocateNoexcept<true>/1000/65536/repeats:20_mean 119320642 ns 119235176 ns 20
BM_MoveRelocateNoexcept<true>/1000/65536/repeats:20_median 119256119 ns 119187012 ns 20
BM_MoveRelocateNoexcept<true>/1000/65536/repeats:20_stddev 190923 ns 180183 ns 20
BM_MoveRelocateNoexcept<false>/100/524288/repeats:20_mean 127031806 ns 126834505 ns 20
BM_MoveRelocateNoexcept<false>/100/524288/repeats:20_median 126939978 ns 126741072 ns 20
BM_MoveRelocateNoexcept<false>/100/524288/repeats:20_stddev 381682 ns 380187 ns 20
BM_MoveRelocateNoexcept<true>/100/524288/repeats:20_mean 95281309 ns 95175234 ns 20
BM_MoveRelocateNoexcept<true>/100/524288/repeats:20_median 95267762 ns 95152072 ns 20
BM_MoveRelocateNoexcept<true>/100/524288/repeats:20_stddev 176838 ns 176834 ns 20
从这些结果来看,在两个基准测试中,可以使用 noexcept-move 的测试相对于它们的 non-noexcept-movable 对应物的速度提高了约 1.3 。