允许这两个函数同时作为不同的函数,因为它们对于“是否可以写入参数”确实不同。直觉上应该是!
函数的重载基于调用者提供的参数。在这里,调用者确实可以提供一个值const
或非const
值,但从逻辑上讲,它应该对被调用函数提供的功能没有影响。考虑:
f(3);
int x = 1 + 2;
f(x);
如果f()
在每种情况下都做不同的事情,那将是非常混乱的!此代码调用的程序员f()
可以对相同的行为有合理的期望,自由添加或删除传递参数的变量,而不会使程序无效。这种安全、理智的行为是您想要证明例外合理的出发点,并且确实有一个 - 当函数重载时行为可以改变ala:
void f(const int&) { ... }
void f(int&) { ... }
所以,我想这就是你觉得不直观的:C++ 为 non-references 提供了比 references 更多的“安全性”(通过仅支持单个实现来强制执行一致的行为)。
我能想到的原因是:
- 因此,当程序员知道非
const&
参数将具有更长的生命周期时,他们可以选择最佳实现。例如,在下面的代码中,返回对T
内部成员的引用可能会更快F
,但如果F
是临时的(如果编译器匹配,则可能是const F&
),则需要按值返回。这仍然非常危险,因为调用者必须知道返回的引用仅在参数存在时才有效。
T f(const F&);
T&f(F&); // 如果更合适,返回类型可以是 const&
const
通过函数调用传播诸如 -ness之类的限定符,如下所示:
常量 T& f(常量 F&);
T&f(F&);
在这里,一些(可能是F
成员)类型的变量在被调用时T
被暴露为const
或非const
基于const
参数的 -ness f()
。当希望扩展具有非成员函数的类时(以保持类极简,或编写可在许多类上使用的模板/算法时),可能会选择这种类型的接口,但这个想法类似于const
成员函数vector::operator[]()
,如您想要的v[0] = 3
允许在非const
向量上,但不允许在一个向量上const
。
当值被值接受时,它们会在函数返回时超出范围,因此不存在涉及返回对部分参数的引用并希望传播其限定符的有效场景。
破解你想要的行为
给定引用规则,您可以使用它们来获得所需的行为类型 - 您只需要注意不要意外修改 by-non-const-reference 参数,因此可能需要采用如下做法非常量参数:
T f(F& x_ref)
{
F x = x_ref; // or const F is you won't modify it
...use x for safety...
}
重新编译的影响
除了为什么语言基于按值参数的 -ness 禁止重载的问题之外const
,还有一个问题是为什么它不坚持const
声明和定义中 -ness 的一致性。
For f(const int)
/ f(int)
... 如果你在头文件中声明一个函数,那么最好不要包含const
限定符,即使实现文件中的后续定义会有它。这是因为在维护期间,程序员可能希望删除限定符......从头文件中删除它可能会触发客户端代码的毫无意义的重新编译,所以最好不要坚持它们保持同步 - 事实上这就是编译器不这样做的原因如果它们不同,则不会产生错误。如果您只是const
在函数定义中添加或删除,那么它接近于代码读者在分析函数行为时可能关心常量的实现。如果你const
在头文件和实现文件中都有它,那么程序员希望使它成为非const
并且忘记或决定不更新标头以避免客户端重新编译,那么它比其他方式更危险,因为程序员const
在尝试分析导致错误的当前实现代码时可能会记住标头中的版本关于函数行为的推理。这都是一个非常微妙的维护问题——只与商业编程真正相关——但这是不要const
在界面中使用的指南的基础。此外,从界面中省略它会更简洁,这对于阅读您的 API 的客户端程序员来说更好。