【问题标题】:Is it OK for function prototypes and function implementation signatures to use const inconsistently?函数原型和函数实现签名可以不一致地使用 const 吗?
【发布时间】:2013-02-24 05:06:32
【问题描述】:

我喜欢尽可能将偶数值参数声明为const,通过搜索SO,我发现that's not too uncommon。像这样:

int add(const int a, const int b)
{
    ...
}

但我想知道:const for values 是我的函数的实现细节,而不是它的接口的一部分。所以把它放到原型中似乎没有必要。

上述函数的这个原型似乎工作得很好:

int add(int a, int b);

但我听说过一些问题,例如将 main 函数的 argc 声明为 const 可能会导致问题:

int main(const int argc, const char* const argv[])

那么这是否意味着int add(int a, int b)int add(const int a, const int b) 根本就不一样?

如果技术上没问题,我应该这样做吗?我也可以在原型中省略变量名,但我没有,所以也许我也不应该省略const

【问题讨论】:

  • 顺便说一句,不要漏掉界面上的名字。当人们希望使用您的函数时,他们会首先查看(有时甚至无法获得定义,具体取决于您的代码的组织或分发方式)。有时(不幸的是)那里的名称是唯一的文档。
  • 使用int add(int a, int b);,编译器有足够的能力为调用具有兼容参数的任何函数生成正确的代码。如果你再定义int add(const int a, const in b) {},那就是兼容的,并且声明是有效的。

标签: c++ c header constants function-prototypes


【解决方案1】:

函数类型不能不同,但你需要知道什么是函数类型的一部分,什么不是。在您的情况下,参数的const 并不重要,因此函数类型是相同的,尽管 声明 看起来与 定义 不同。 p>

在你的情况下是

8.3.5 函数[dcl.fct]

5 一个名称可用于单个范围内的多个不同功能;这是函数重载(第 13 条)。函数的所有声明都应在返回类型和参数类型列表中完全一致。使用以下规则确定函数的类型。每个参数的类型(包括函数参数包)由其自己的 decl-specifier-seq 和声明符确定。后 确定每个参数的类型,将“T的数组”或“返回T的函数”类型的任何参数分别调整为“指向T的指针”或“返回T的函数的指针”。在生成参数类型列表后,任何修改参数类型的顶级 cv 限定符都会在形成函数类型时被删除。转换后的参数类型的结果列表以及省略号或函数参数包的存在与否是函数的参数类型列表。 [注:此转换不 影响参数的类型。例如,int(*)(const int p, decltype(p)*)int(*)(int, const int*) 是相同的类型。 ——尾注]

看起来,它需要一些解释,所以我们开始:在我们的例子中,重要的句子是:在生成参数类型列表之后,任何修改参数类型的顶级 cv-qualifiers 被删除形成函数类型。

这意味着所有顶级 cv-qualifier 都被删除。为了解释顶级的含义,我将用非法的方式编写类型来强调 const 所指的内容:

  • const int = (const (int)) -> 这是顶级const
  • const int* = ((const (int))*) -> 不是顶级,是二级
  • const int* const = (((const (int))*) const) -> 第二个const 在顶层
  • const int& = ((const (int))&) -> 不是顶级的

我希望这能消除对函数类型的一些误解。

对于您的其他问题:我建议保持声明和定义相同,因为它可能会使人们感到困惑(就像这个问题所证明的那样;)。

对于你给出的main的例子:

int main( const int argc, const char* const argv[] )

是,根据上面的标准引用,相当于:

int main( int argc, const char* const* argv )

所以为argv 添加的const 最终不会作为顶级const 被删除,因此它是main 的格式错误的函数类型,它期望:

int main( int argc, char** argv )

关于省略参数名称的最后一个问题:我不会这样做,因为对我来说,它们是函数文档的一部分。它们传达函数的意图和语义(如果您明智地选择它们)。

【讨论】:

  • 你在说什么? const 在签名中非常重要。
  • @LuchianGrigore:在某些情况下(如上述),它不是签名的一部分。在投反对票之前弄清楚事实。
  • @LuchianGrigore 您可以尝试同时定义void foo(int)void foo(const int) 并得到重新定义错误。很遗憾,我现在无法检查标准。
  • @TonyTheLion:是的,就是这样。请注意,这表示 top-level,这意味着 const int 减少为 int,但 const int* 保持不变。另外:const int* const 变为 const int*
  • 很抱歉,您的轻率投反对票(现在 +1),这只是您认为自己是对的情况之一,然后 bam!,C++ 说不 :)
【解决方案2】:

对于函数的声明和定义之间的函数参数,应该可以使用不同的顶级const,但请注意,并非所有编译器都没有错误。例如,Oracle Sun 编译器有一个 long standing issue,它会以不同的方式处理 int f(int)int f(const int)

为了避免您真正想要通过 const 引用传递的任何混淆可能性,我通常建议在公共函数声明中避免顶级 const 并避免可能的编译器问题,我也会在函数定义中避免它。

(请注意,将参数列表中的char ** 更改为const char* const argv[] 并不是添加顶级常量,而是真正的签名更改。char** 仅相当于函数参数列表中的char** const。)

【讨论】:

  • 但是你会尽你所能吗?如果我不使用 const 参数会感觉不一致,但我不相信编译器问题和冗长的接口不会付出太大的代价。
  • @futlib:我在合适的地方使用const,并且有实际的好处;我不一定“尽我所能”。
【解决方案3】:

const for values 是我的函数的一个实现细节,而不是它的接口的一部分。

这是你思维的缺陷,当谈到引用和指针const 与接口有关时,它告诉使用接口的程序员你传递的内容不会被函数改变。它告诉编译器同样的事情,并将程序员绑定到这个合约。

带有const 参数的函数和带有非const 参数的函数是不同的。

然而,按值传递会复制参数,在这种情况下更改它们不是问题。您的 ints 是按值传递的,而 const 不会差别很大。

但是,我个人认为这不是滥用接口不一致的理由,并让接口在一个地方与另一个地方不同。

虽然通过 ref 传递,但对const 的引用与对非const 的引用完全不同。

【讨论】:

  • 虽然当你按值传递时,函数不会改变你传递的内容......
  • 这不是真的。值总是被复制的,调用者不可能受到所述值的常量性的影响,因为它无法访问它。这纯粹是实现细节。
  • 是的,值上的 const 是无关紧要的。
  • @LuchianGrigore:好的,但意图是什么?该函数是否会在内部修改它获得的值的 copy 与客户端无关...
  • @LuchianGrigore: 顺便说一句,this one 编译...所以值类型的顶级简历似乎并不重要。
猜你喜欢
  • 1970-01-01
  • 2011-06-02
  • 2013-07-10
  • 1970-01-01
  • 2012-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-28
相关资源
最近更新 更多