每周技巧 #109:函数声明中有意义的 `const`

本节阅读量:

本文翻译自 Abseil 官网的 Tip of the Week #109: Meaningful const in Function Declarations

原文最初作为 totw/109 发布于 2016 年 1 月 14 日。

作者:Greg Miller

本文会解释什么时候 const 在函数声明中有意义,什么时候它没有意义并且最好省略。不过首先,让我们简要解释一下 declaration(声明)和 definition(定义)这两个术语的含义。

考虑下面的代码:

1
2
3
4
void F(int);                     // 1:F(int) 的声明
void F(const int);               // 2:F(int) 的重新声明
void F(int) { /* ... */ }        // 3:F(int) 的定义
void F(const int) { /* ... */ }  // 4:错误:F(int) 的重新定义

前两行是函数声明。函数声明告诉编译器函数的签名和返回类型。在上面的例子中,函数签名是 F(int)。函数参数类型的 const 性会被忽略,所以两个声明等价(见 “Overloadable declarations”)。

上面代码中的第 3 行和第 4 行都是函数定义。函数定义也是一种声明,但定义还包含函数体。因此,第 3 行是签名为 F(int) 的函数定义。类似地,第 4 行也是同一个函数 F(int) 的定义,这会导致链接期错误。允许多个声明,但只允许一个定义。

尽管第 3 行和第 4 行的定义声明定义了同一个函数,但由于它们的声明方式不同,其函数体内部存在差异。在第 3 行的定义中,函数参数变量在函数内部的类型是 int(也就是非 const)。另一方面,第 4 行的定义会在函数内部产生一个类型为 const int 的函数参数变量。

函数声明中有意义的 const

并非函数声明中所有 const 限定都会被忽略。引用 C++ 标准中 “Overloadable declarations” ([over.load]) 的说法(强调为原文所加):

嵌入在参数类型说明中的 const 类型说明符是有意义的,可用于区分重载函数声明。

下面是 const 有意义且不会被忽略的例子:

1
2
3
4
void F(const int* x);                  // 1
void F(const int& x);                  // 2
void F(std::unique_ptr<const int> x);  // 3
void F(int* x);                        // 4

在上面的例子中,参数 x 本身从未被声明为 const。每个函数都接收一个名为 x、但类型不同的参数,因此形成有效的重载集。第 1 行声明了一个函数,它接收“指向 const int 的指针”。第 2 行声明了一个函数,它接收“指向 const int 的引用”。第 3 行声明了一个函数,它接收“指向 const int 的 unique_ptr”。这些 const 用法都很重要,也不会被忽略,因为它们是参数类型说明的一部分,而不是影响参数 x 本身的顶层 const 限定。

第 4 行很有意思,因为它完全不包含 const 关键字,而且根据本文开头提到的理由,乍看可能像是等价于第 1 行声明。事实并非如此,第 4 行是有效且不同的声明,原因是只有参数类型说明的顶层、最外层 const 限定会被忽略。

为了完成这个例子,我们再看几个 const 没有意义且会被忽略的例子。

1
2
3
void F(const int x);          // 1:声明 F(int)
void F(int* const x);         // 2:声明 F(int*)
void F(const int* const x);   // 3:声明 F(const int*)

经验法则

虽然我们中很少有人会真正掌握 C++ 中所有令人愉悦的晦涩之处,但尽力理解游戏规则很重要。这会帮助我们写出其他遵循相同规则、玩同一个游戏的 C++ 程序员能够理解的代码。因此,我们必须理解函数声明中什么时候 const 限定有意义,什么时候会被忽略。

尽管 Google C++ 风格指南没有给出官方指导,也没有一个普遍接受的单一意见,下面是一组合理的指导原则:

  1. 永远不要在非定义的函数参数声明中使用顶层 const(并注意不要复制/粘贴无意义的 const)。它没有意义,会被编译器忽略,是视觉噪音,还可能误导读者。
  2. 在函数参数定义中是否使用顶层 const,由你(或你的团队)自行决定。你可以使用与何时把函数局部变量声明为 const 相同的理由。

每周技巧 #108:避免 `std::bind`

上一节

每周技巧 #112:emplace 与 push_back

下一节