为什么将未使用的返回值转换为void?

140
int fn();

void whatever()
{
    (void) fn();
}

将未使用的返回值强制转换为void是否有任何理由,还是我认为这完全是浪费时间?

10个回答

94

David的答案已经涵盖了这一做法的动机,即明确向其他“开发人员”展示你知道该函数有返回值,但你明确忽略了它。

这是一种确保必要的错误代码始终得到处理的方法。

我认为对于C++来说,这可能是我唯一喜欢使用C风格强制类型转换的地方,因为在此处使用完整的静态转换符号感觉就像是过度设计。最后,如果您正在审查编码标准或撰写编码标准,则还应明确说明调用重载运算符(不使用函数调用符号)也应从中豁免:

class A {};
A operator+(A const &, A const &);

int main () {
  A a;
  a + a;                 // Not a problem
  (void)operator+(a,a);  // Using function call notation - so add the cast.

这是我唯一喜欢使用C风格转换的地方。虽然在我的编码标准中,我也会在"a + a;"中添加(void)(当然要正确地加括号 :p)。 - Johannes Schaub - litb
10
有点跑题,但是为什么你会这样写"a + a;"而不使用返回值呢?这看起来像是滥用副作用,使代码意图变得模糊。 - Rob K
5
常用的一个例子是'a = a',它返回被赋值的对象(对于所有良好行为的类而言!:))。另一个例子是:os<<"Hello World"<<std::endl。两者都返回"os"对象。 - Richard Corden

55

在工作中,我们使用该技巧来承认函数具有返回值,但是开发人员已经断言可以安全地忽略它。由于您将问题标记为C ++,所以您应该使用 static_cast :

static_cast<void>(fn());

就编译器而言,将返回值转换为void几乎没有意义。


它是否抑制了有关未使用返回值的警告? - Paul Tomblin
4
我使用 g++,即使进行了这样的强制转换,它仍会发出警告。 - klew
2
你使用哪个警告选项?-Wunused-value在我的环境中不会触发警告。 - Mykola Golubyev
没有任何选项,我会收到警告:警告:忽略‘int fscanf(FILE,const char,...)’的返回值,声明为带有warn_unused_result属性 - 但这是在我安装最新的Ubuntu时开始的。我还没有尝试禁用它。 - klew
这是使用__attribute__((warn_unused_result)),默认情况下警告在C89之前被删除,因此该操作仅适用于非常旧的代码。 - Steve-o
显示剩余2条评论

44

这样做的真正原因可以追溯到用于C代码的工具,称为lint

它分析代码以寻找可能的问题并发出警告和建议。如果一个函数返回一个值,但没有检查该值,lint会警告是否是意外的。为了消除这个警告,你需要将函数调用转换为(void)类型。


2
它可能始于这种方式,但现在大多数工具都有其他机制来抑制此类警告。此外,不考虑其起源,特别是在安全关键代码领域内,这已成为“记录”开发人员意图的常规方式。 - Richard Corden
7
使用-Wall选项,GCC会对此发出警告。 - greyfade

26

强制转换为 void 用于抑制编译器警告,提示未使用的变量和未保存的返回值或表达式。

标准(2003) 在 §5.2.9/4 中指出:

任何表达式都可以明确地转换为“cv void”类型,表达式的值被丢弃

因此,您可以编写以下代码:

//suppressing unused variable warnings
static_cast<void>(unusedVar);
static_cast<const void>(unusedVar);
static_cast<volatile void>(unusedVar);

//suppressing return value warnings
static_cast<void>(fn());
static_cast<const void>(fn());
static_cast<volatile void>(fn());

//suppressing unsaved expressions
static_cast<void>(a + b * 10);
static_cast<const void>( x &&y || z);
static_cast<volatile void>( m | n + fn());

所有表单都是有效的。通常我会将其缩短为:

//suppressing  expressions
(void)(unusedVar);
(void)(fn());
(void)(x &&y || z);

这也没问题。


2
除了它并不总是关闭编译器警告之外。gcc似乎对转换为void类型没有反应。 - hookenz
@Matt:你使用的是哪个版本的GCC?你能在ideone.com或stacked-crooked.com上发布你的代码吗(后者更好)? - Nawaz
1
标准的“discarded”措辞是否意味着linter不应该发出警告? - Ciro Santilli OurBigBook.com

11

从C++11开始,您还可以这样做:

std::ignore = fn();

在标记了[[nodiscard]]的函数上,这应该可以实现相同的结果。


11
自从c++17以来,我们有了[[maybe_unused]]属性,可以代替void转换。

1
C++17 还添加了 nodiscard,可能会引起兴趣:https://dev59.com/0XRB5IYBdhLWcg3wNk53#61675726。另外,我认为你不能直接将 [[maybe_unused]] 应用于调用本身,只能应用于接受调用返回值的虚拟变量?如果这是正确的,那就有点奇怪了。 - Ciro Santilli OurBigBook.com

6

将其转换为void类型是无成本的。这只是告诉编译器如何处理它的信息。


1
如果编写代码以及后续的阅读、理解(然后忽略)不需要花费时间,那么这是“免费”的。在我看来,编写(void)会浪费我的时间和精力,并让我怀疑作者是否知道表达式的工作原理。 - wallyk

4

对于您程序的功能而言,将其转换为void是没有意义的。我还会认为,不应该像David的答案中建议的那样使用它来向阅读代码的人传递信号。如果您想要传达有关您意图的信息,最好使用注释。添加这样的转换只会看起来奇怪,并引发可能原因的问题。仅代表个人观点...


3
这是一种常见的模式,明确告诉他人你不关心返回值,而不是只是忘记处理它。 - Spidey
对于程序功能而言,将其转换为void类型是毫无意义的。但从自我记录和编译时警告数量的角度来看,这种做法确实不可取。你不应该使用它来向阅读代码的人传达某些信息[...]最好使用注释。当代码本身就能解释清楚时,最好不要加注释。同时,这也可以让编译器保持有效的警告状态。 - underscore_d
"像这样添加一个强制类型转换只会看起来很奇怪,并引发关于可能原因的疑问。" 我发现其中之一,这就是我正在阅读的原因。对于我这个没有经验的人来说,它似乎像是神秘的堆栈滥用。 - coolhandle01

4

C++17 [[nodiscard]]

C++17标准化了“返回值被忽略”的问题,并引入了一个属性。

因此,我希望符合规范的实现只会在使用nodiscard时发出警告,而不会在其他情况下发出警告。

示例:

main.cpp

[[nodiscard]] int f() {
    return 1;
}

int main() {
    f();
}

编译:

g++ -std=c++17 -ggdb3 -O0 -Wall -Wextra -pedantic -o main.out main.cpp

结果:

main.cpp: In function ‘int main()’:
main.cpp:6:6: warning: ignoring return value of ‘int f()’, declared with attribute nodiscard [-Wunused-result]
    6 |     f();
      |     ~^~
main.cpp:1:19: note: declared here
    1 | [[nodiscard]] int f() {
      | 

以下所有方法都可以避免警告:
(void)f();
[[maybe_unused]] int i = f();

我无法直接在f()调用上使用maybe_unused

[[maybe_unused]] f();

提供:

main.cpp: In function ‘int main()’:
main.cpp:6:5: warning: attributes at the beginning of statement are ignored [-Wattributes]
    6 |     [[maybe_unused]] f();
      |     ^~~~~~~~~~~~~~~~

(void) 强制转换似乎不是强制性的,但在标准中“鼓励”使用:如何有意地丢弃 [[nodiscard]] 返回值?

此外,从警告消息中可以看出,解决警告的“方法”之一是添加 -Wno-unused-result

g++ -std=c++17 -ggdb3 -O0 -Wall -Wextra -pedantic -Wno-unused-result -o main.out main.cpp

虽然我当然不建议像这样全局忽略警告。

C++20还允许您添加一个原因到nodiscard中,如[[nodiscard("reason")]]所述:https://en.cppreference.com/w/cpp/language/attributes/nodiscard

GCC warn_unused_result属性

[[nodiscard]]标准化之前,以及在C语言最终决定标准化属性之前,GCC使用warn_unused_result实现了完全相同的功能:

int f() __attribute__ ((warn_unused_result));

int f() {
    return 1;
}

int main() {
    f();
}

这将会给出:

main.cpp: In functionint main()’:
main.cpp:8:6: warning: ignoring return value ofint f()’, declared with attribute warn_unused_result [-Wunused-result]
    8 |     f();
      |     ~^~

需要翻译的内容如下:

需要注意的是,由于ANSI C没有针对此进行标准化,因此ANSI C并未指定哪些C标准库函数具有属性,因此实现者已经就应该或不应该用warn_unused_result做出了自己的决策。一般情况下,您将必须使用(void)转换来忽略任何对标准库函数的调用返回,以完全避免在任何实现中产生警告。

在GCC 9.2.1,Ubuntu 19.10中进行了测试。


你写道:“我无法直接在f()调用上使用maybe_unused”,我认为这是正确的,但在标准中,我看到了这个例子:[[maybe_unused]] void f([[maybe_unused]] bool thing1, [[maybe_unused]] bool thing2) { [[maybe_unused]] bool b = thing1 && thing2; assert(b); },在你的回答中提到这一点也是有好处的,不是吗? - albert
@albert 你好,我们可以引用标准,但在这种特定情况下,你是指标准引用你提到的 f(); 调用意味着什么? - Ciro Santilli OurBigBook.com
据我理解,当使用[[nodiscard]]int f(){...}时,在调用时不需要返回值,必须使用(void)f();static_cast<void>f();。当使用[[maybe-unused]]f(){...}时,可以使用f();。因此,我认为为了完整起见,提及这种可能性(带有示例)是一件好事。当然,所使用的风格取决于用户包在其编码规则/偏好中采用的风格。 - albert

3
此外,当验证您的代码是否符合MISRA(或其他)标准时,静态分析工具如LDRA将不允许您调用返回类型为void的函数而没有它返回一个值,除非您显式地将返回值转换为(void)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接