[[nodiscard]] 是编译器静态检查辅助属性,提示函数返回值不应被忽略,不改变运行时行为;c++17引入,需编译器支持并启用相应警告选项。
![C++中的属性(Attributes, [[nodiscard]])是什么?(如何辅助编译器检查) C++中的属性(Attributes, [[nodiscard]])是什么?(如何辅助编译器检查)](https://img.php.cn/upload/article/001/431/639/177127459932998.jpg)
[[nodiscard]] 是编译器的“提醒开关”,不是运行时约束
它告诉编译器:这个函数的返回值不该被忽略;如果调用了却没用上返回值,就报警告(或错误,取决于编译器设置)。它不改变函数行为,也不影响生成的机器码,纯粹是静态检查辅助。
常见错误现象:[[nodiscard]] int parse_int(const std::String& s); 被写成 parse_int("42");(没接返回值),GCC/Clang 默认只发警告,容易被忽视;MSVC 默认更严格,可能直接报错。
- 使用场景:资源获取(
std::unique_ptr构造)、状态转换(std::optional::value_or())、错误码返回(自定义Result<t></t>类型) - 参数差异:它本身不接受参数,但可以和
[[nodiscard("reason")]]一起用(C++20),让警告信息更具体,比如[[nodiscard("ownership transferred")]] - 兼容性影响:C++17 引入,老项目若用 C++14 编译会直接报错;需确保构建系统设对
-std=c++17或更高
自己加 [[nodiscard]] 前先确认返回值真有语义意义
乱加反而会干扰开发者——比如 [[nodiscard]] bool is_valid() const 被调用却不检查,确实该警告;但 [[nodiscard]] size_t size() const 被忽略(比如只用于断言),就显得啰嗦。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 优先加在「调用后不消费返回值会导致逻辑缺陷」的地方:如内存分配、错误传播、新对象构造
- 避免加在纯访问器(accessor)上,除非该访问器有副作用或隐含状态变更(罕见)
- 第三方库已加的(如
std::vector::data()在 C++20 后加了),别覆盖;自己封装时可继承属性:[[nodiscard]] auto get_ptr() { return vec.data(); }不自动继承,得显式写
警告不触发?检查编译器级别和诊断开关
写了 [[nodiscard]] 却没警告,大概率是编译器没开对应检查,或者版本太低。
常见原因:
- 编译器版本不够:GCC 7+、Clang 4+、MSVC 2017 15.3+ 才支持;旧版识别不了,直接跳过
- 没启用警告:GCC/Clang 默认只开
-Wall不包含nodiscard检查,需额外加-Wunused-result(GCC)或-Wunused-result(Clang) - 用了
static_cast<void></void>或逗号表达式“消音”:(void)func();或(func(), 0);会绕过检查,这是有意为之的豁免方式,不是 bug
和返回类型强相关的陷阱:auto + [[nodiscard]] 容易漏掉
用 auto 推导返回类型时,如果忘了写 [[nodiscard]],属性不会自动附着到推导出的类型上——它只属于函数声明本身。
错误写法:
auto create_resource() { return std::make_unique<T>(); } // ❌ 没属性
正确写法:
[[nodiscard]] auto create_resource() { return std::make_unique<T>(); } // ✅
更隐蔽的问题:模板函数中,[[nodiscard]] 必须写在每个特化或显式实例化前,不能只靠主模板带过去;SFINAE 或 requires 子句不影响属性绑定位置。
容易被忽略的是:当函数返回 [[nodiscard]] 类型(如 std::expected)时,不等于函数本身有该属性——仍需手动标注,否则调用点不会受检。