警告“未引用的形参/局部变量”的消除方法

如果我们编译以下代码:

#include 

int main(int argc, char** argv)
{
    int n;
    int nRet = printf("Hello, world!");
    return 0;    
}

编译器一般会发出以下警告(VS2015):

1>f:\mycode\cpptest\main.cpp(55): warning C4100: “argv”: 未引用的形参
1>f:\mycode\cpptest\main.cpp(55): warning C4100: “argc”: 未引用的形参
1>f:\mycode\cpptest\main.cpp(67): warning C4101: “a”: 未引用的局部变量
1>f:\mycode\cpptest\main.cpp(68): warning C4189: “nRet”: 局部变量已初始化但不引用

编译器认为,既然我们已经声明/定义了某变量,那我们就有使用它的意图。所以当它检测到我们未对此变量进行实际的使用时,就会发出警告,提醒我们检查代码是否存在错误。

为了代码编译时输出窗口的整洁(以使有价值的提示信息更醒目),我们通常会消除掉此类警告(当然是在我们确认这是有意为之之后)。而在消除此类警告时,隐式地,我们一般有以下四种期望:

  • 编译器不再发出此警告;
  • 不会引起代码逻辑变化;
  • 不会造成性能损失;
  • 不会对代码维护造成负担,包括阅读和修改;

以下提供两种经典解决方案,并分析网上流传的一些方法为什么不可取。

一、宏UNREFERENCED_PARAMETER

Windows开发人员使用宏定义方案。在Windows平台下,可以在中找到以下宏定义:

#define UNREFERENCED_PARAMETER(P)          (P)

使用方法如下:

int main(int argc, char** argv)
{
    UNREFERENCED_PARAMETER(argc); // 手动醒目
    UNREFERENCED_PARAMETER(argv); // 手动醒目

    int n;
    UNREFERENCED_PARAMETER(n); // 手动醒目
    int nRet = printf("Hello, world!");
    UNREFERENCED_PARAMETER(nRet); // 手动醒目
    return 0;    
}

因为实际上,代码被宏展开为如下形式:

int main(int argc, char** argv)
{
    (argc); // 手动醒目
    (argv); // 手动醒目

    int n;
    (n); // 手动醒目
    int nRet = printf("Hello, world!");
    (nRet); // 手动醒目
    return 0;    
}

,即argcargvnnRet确实被使用(作为一条单独的语句),所以警告不会再产生。
如果严格按照这样一个变量一条语句的写法,代码逻辑就绝不会发生变化;同时,在release模式下,这些代码完全会被优化掉,所以对性能也不会造成影响。

另外,宏的名称UNREFERENCED_PARAMETER已经说明,这是一个未被引用的参数,所以在阅读时不仅不会造成障碍,反而会有助于理解;而在代码修改时,如果真正使用了些变量,直接将此句删除,也是很简单的一件事。

但是,实际上在Windows中,此宏仅被用来消除warning C4100: 未引用的形参警告,因为宏的名称是UNREFERENCED_ PARAMETER。所以如果在我们的项目中应用此解决方案时,最好自己定义一个名称更应景的宏,譬如:

#define UNREFERENCED_VALUE(P)          (P)

以同时完美应用于上述三种警告。

二、模板空函数PX_UNUSED()

NVIDIA的PhysX项目开发人员使用模板空函数解决方案。在PhysX开源代码的include\foundation\PxPreprocessor.h中可以找到如下定义(同时还有某些对此方法的疑问,下面各节将会回答这些疑问):

// avoid unreferenced parameter warning (why not just disable it?)
// PT: or why not just omit the parameter's name from the declaration????
template <class T> PX_CUDA_CALLABLE PX_INLINE void PX_UNUSED(T const&) {}

使用方法如下:

int main(int argc, char** argv)
{
    PX_UNUSED(argc); // 手动醒目
    PX_UNUSED(argv); // 手动醒目

    int n;
    PX_UNUSED(n); // 手动醒目
    int nRet = printf("Hello, world!");
    PX_UNUSED(nRet); // 手动醒目
    return 0;    
}

首先,原本未被引用的变量确实被用来调用函数了,所以警告被消除。应该注意到,在PX_UNUSED()实现时有一个细节,它没有给出参数的具体名称const T&,所以警告不会再传递(见“三、为什么不应该直接删除函数参数名”)。

其次,因为仅被用来调用空函数,且是传引用方法,所以代码逻辑不会被影响;而空函数在release模式下也会被优化掉,所以也不会产生性能影响,顶多在编译时生成模板函数增加了一点点时间。

最后,此函数被使用时,同样醒目且易删除,所以也不会对代码维护造成额外的负担。

相对于宏定义的方式,我比较赞成此种解决方案,因为前者有可能因为误用而造成莫名其妙的错误。

三、为什么不应该直接删除函数参数名

首先,此种方法只能解决警告“warning C4100: 未引用的形参“,而对其他两种警告无能为力,因为有的时候未引用的变量确实是不方便删除的。例如以下代码在release下会引发警告,而我们又需要在deubg模式下保留它:

bool ok = doSth(...);
ASSERT(ok);

另一方面,直接删除函数参数名会对代码维护造成负担。在阅读一个.cpp文件中的函数时,我们通常期望通过参数的名称捕捉到关于函数功能或实现方式的线索,而且这种期望是下意识的,是一种连续的”流“。如果突然某个函数缺失了参数名称,不仅导致函数的线索的消失,同时也会打断这种下意识”流“,打断阅读代码的状态。同时,此时代码阅读者会下意识地跳转到头文件来查看函数的声明,以期望找回”丢失的参数名“,这又浪费了额外的时间和精力。而且,在修改代码时,如果突然此变量又被引用,那么维护人员将又不得不将从头文件中找回参数名,并加回.cpp文件中,对比简单地删除,这种操作较为烦琐。所以,此种消除警告的方法应该是禁止的。

但是有一个例外,就是如”二、模板空函数PX_UNUSED()“所示的函数PX_UNUSED(),此函数刻意地删除参数名以实现其目的。

四、为什么不应该直接注释函数参数名

参考上一节”三、为什么不应该直接删除函数参数名“,首先此方法仅对一种警告起作用。
其次是习惯问题。大多数程序员的习惯,是在函数实现文件是注释掉函数参数的默认值:

// .h文件中,函数的声明
void doSth(int n = 0);

// .cpp文件中,函数的实现
void doSth(int n/* = 0*/)
{
    ...
}

如果突然出现这样的代码:

void doOth(int/* m*/)
{
    ...
}

也会对代码阅读产生轻微的障碍。如果代码是小范围传播,倒也没有问题;如果是大型,由多人参与的项目,则应当照顾的大多数人的习惯。所以,此种方法也不建议使用。

五、为什么不应该直接抑制此类警告

有人提议,使用预处理指令#pragma warning(disable:4100)抑制此类警告。此种方法应该是严格禁止的!

编译器的警告并不是无的放矢,有时候,确实可能由于变量名拼写错误从而触发此警告,如下例子:

int sum(int n1, int n2, int n3)
{
    return n1 + n2 + n2;
}

如果抑制了此警告,那你就失去了在BUG萌芽时就将其消灭的机会。后面,你有可能要花数个小时去寻找这个BUG!

从另一个角度讲,这也反映了一个人对待代码的态度。如果他对编译器发出的警告仅感到厌倦从而抑制了之,而不是去确认问题确是否真的存在,或寻找一种更无害的消除方式,那么他将不能成为一个好的程序员。从我个人角度来讲,我不愿意与这样的人共事。

你可能感兴趣的:(C++)