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