为清晰起见,是否应使用返回类型的无用类型限定符?

当我们在头文件中有原型时,我们的静态分析工具会抱怨“返回类型上无用的类型限定符”,例如:

const int foo(); 

我们这样定义它是因为函数返回一个永远不会改变的常量,认为API似乎更清晰。

我觉得这类似于为了清楚明确地将全局变量初始化为零,即使C标准已经声明如果未明确初始化所有全局变量将被初始化为零。 在一天结束时,它真的没关系。 (但静态分析工具并没有抱怨。)

我的问题是,有什么理由可以导致问题吗? 我们是否应该忽略该工具产生的错误,或者我们是否应该以不太清晰和一致的API的可能成本来安抚该工具? (它返回该工具没有问题的其他const char*常量。)

通常,您的代码最好尽可能准确地描述正在发生的事情。 你得到这个警告是因为const int foo();中的const int foo(); 基本上没有意义。 如果您不知道const关键字的含义,那么API似乎更清晰。 不要超载这样的含义; static已经足够糟糕了,并且没有理由增加更多混淆的可能性。

const char *意味着与const int不同的东西,这就是为什么你的工具不会抱怨它。 前者是一个指向常量字符串的指针,这意味着调用返回该类型的函数的任何代码都不应该尝试修改字符串的内容(例如,它可能在ROM中)。 在后一种情况下,系统无法强制您不对更改的int进行更改,因此限定符无意义。 与返回类型更接近的是:

 const int foo(); char * const foo2(); 

这将导致静态分析发出警告 – 向返回值添加const限定符是无意义的操作。 只有你有一个引用参数(或返回类型)时才有意义,就像你的const char *例子一样。

事实上,我刚刚做了一个小测试程序,GCC甚至明确警告过这个问题:

 test.c:6: warning: type qualifiers ignored on function return type 

因此,不只是您的静态分析程序在抱怨。

您可以使用不同的技术来说明您的意图,而不会使工具不快乐。

 #define CONST_RETURN CONST_RETURN int foo(); 

const char *没有问题,因为它声明了一个指向常量字符的指针,而不是一个常量指针。

暂时忽略constfoo()返回一个值。 你可以做

 int x = foo(); 

并将foo()返回的值赋给变量x ,方法与执行的方法大致相同

 int x = 42; 

将值42赋给变量x。
但你不能改变42 …或foo()返回的值。 假设无法更改从foo()返回的值,通过将const关键字应用于foo()类型,什么也不做。

不能是constrestrictvolatile )。 只有对象可以具有类型限定符。


对比

 const char *foo(); 

在这种情况下, foo()返回指向对象的指针。 返回值指向的对象可以是const限定的。

int由副本返回。 它可能是const的副本,但是当它被分配给其他东西时,由于它是可分配的,这个东西不能定义为const。

关键字const在语言中具有特定的语义,而在这里你滥用它本质上是一个注释。 而不是增加清晰度,而是暗示对语言语义的误解。

const int foo()const char* foo()非常不同。 const char* foo()返回一个不允许更改其内容的数组(通常是一个字符串)。 想想以下两者之间的区别:

  const char* a = "Hello World"; 

 const int b = 1; 

a仍然是一个变量,可以分配给其他不能更改的字符串,而b不是变量。 所以

 const char* foo(); const char* a = "Hello World\n"; a = foo(); 

是允许的但是

 const int bar(); const int b = 0; b = bar(); 

即使使用bar()const声明,也是不允许的。

是。 我建议“明确地”编写代码,因为在阅读代码时,任何人(包括你自己)都会清楚地表达你的意思。 您正在编写代码供其他程序员阅读 ,而不是取悦编译器和静态分析工具的想法!

(但是,您必须要小心,任何此类“不必要的代码”都不会导致生成不同的代码!)

显式编码的一些示例提高了可读性/可维护性:

  • 我在算术表达式的部分周围放置括号,以明确指定我想要发生的事情。 这使得任何读者都清楚我的意思,并使我不必担心(或犯错误)优先规则:

     int a = b + c * d / e + f;  //难以阅读 - 需要知道优先权
     int a = b +((c * d)/ e)+ f;  //易于阅读 - 清晰明确的计算
    

  • 在C ++中,如果覆盖虚函数,那么在派生类中,您可以在不提及“虚拟”的情况下声明它。 任何阅读代码的人都不能说这是一个虚拟function,这可能是灾难性的误导! 但是,您可以安全地使用virtual关键字:

      virtual int MyFunc() 

    这使得阅读你的类标题的人明白这个方法是虚拟的。 (这个“C ++语法错误”在C#中通过要求在这种情况下使用“override”关键字得到修复 – 如果有人需要它更多certificate错过了“不必要的虚拟”是一个非常糟糕的主意)

这些都是明显的例子,其中添加“不必要的”代码将使代码更易读并且更不容易出错。