[[nodiscard]] 不是加了就安全,因其仅在调用处对完全丢弃返回值触发警告,且依赖编译器警告级别、需逐个标注模板/重载,移动语义或类型差异(如 vector::at() 有而 operator[] 无)可能导致失效。
![c++编译器属性实战:[[nodiscard]]、[[likely]]提升代码健壮性【契约编程】](https://img.php.cn/upload/article/001/431/639/176880580756110.jpg)
为什么 [[nodiscard]] 不是加了就安全?
它只在调用处触发编译器警告,且仅当返回值被完全丢弃时才生效——比如 foo(); 或 (void)foo(); 会绕过检查,而 auto x = foo(); 却不使用 x 也不会报警。
- 必须配合编译器警告级别启用:GCC/Clang 需
-Wnodiscard(C++17 起默认开启部分检查),MSVC 需/wd5045或更高警告等级 - 对重载函数、模板实例化要逐个标注;
std::vector::at()有[[nodiscard]],但operator[]没有,这是有意为之的设计差异 - 自定义类型返回
[[nodiscard]]对象时,移动语义可能抑制警告(如return std::move(result);在某些旧版 Clang 中失效)
[[likely]] 和 [[unlikely]] 真的能提速吗?
它们不改变逻辑,只向编译器提供分支预测偏好。实际收益高度依赖 CPU 架构、运行时数据分布和优化等级。O2/O3 下效果较明显,-O0 基本无作用。
- 只对
if、switch的具体分支标签生效,不能修饰整个函数或表达式 - 错误标注反而降低性能:把高频路径标成
[[unlikely]]会导致分支预测失败率飙升 - Clang 对嵌套条件支持较好,GCC 12+ 才稳定支持
[[likely]]在if constexpr中(C++20)
if (ptr == nullptr) [[unlikely]] {
throw std::invalid_argument("null pointer");
} else [[likely]] {
return *ptr;
}契约编程中如何组合使用这两个属性?
核心是让接口意图可验证、关键路径可感知。比如资源获取函数必须检查返回值,错误分支应明确标记为小概率事件。
- 工厂函数、解析函数、系统调用封装建议统一加
[[nodiscard]],避免静默失败 - 异常路径、边界校验失败、日志采样开关等低频逻辑,用
[[unlikely]]引导代码布局,减少热路径指令缓存污染 - 注意:二者不能解决运行时逻辑错误,只是强化静态契约;若函数本身未做充分校验(如没检查输入参数),加了
[[nodiscard]]也拦不住误用
[[nodiscard]] std::optionalparse_int(const std::string& s) { char* end; long val = std::strtol(s.c_str(), &end, 10); if (*end != '\0') [[unlikely]] { return std::nullopt; } return static_cast (val); }
跨编译器兼容性怎么处理?
不是所有编译器都原生支持 C++20 属性语法,尤其在混合构建环境中(如 Windows 上用 MSVC 编译但链接 GCC 编译的库)。
立即学习“C++免费学习笔记(深入)”;
- 用宏封装最稳妥:
#define NODISCARD [[nodiscard]],并在头文件顶部检测标准版本与编译器特性 - MSVC 19.26+、GCC 9+、Clang 8+ 支持原生
[[nodiscard]];[[likely]]则需 GCC 9+/Clang 10+/MSVC 19.28+ - 旧编译器回退方案:GCC/Clang 可用
__attribute__((warn_unused_result))替代[[nodiscard]],但无对应likely回退机制
实际项目里最容易忽略的是:属性只作用于声明,不继承。类成员函数加了 [[nodiscard]],派生类重写后必须显式再加,否则契约断裂。











