语言规范允许实现通过在全局<cmath>
命名空间中声明(和定义)标准函数,然后通过使用声明将它们带入命名空间来实现。未指定是否使用此方法std
20.5.1.2 头文件
4 [...] 然而,在 C++ 标准库中,声明(除了在 C 中定义为宏的名称)在 namespace 的命名空间范围 (6.3.6) 内std
。std
未指定这些名称(包括在第 21 到 33 条和附件 D 中添加的任何重载)是否首先在全局名称空间范围内声明,然后通过显式使用声明(10.3.3)注入名称空间。
显然,您正在处理决定遵循这种方法的实现之一(例如 GCC)。即您的实现提供了::abs
,而std::abs
只是“引用”到::abs
.
在这种情况下剩下的一个问题是,为什么除了标准之外::abs
您还能够声明自己的::abs
,即为什么没有多重定义错误。这可能是由某些实现(例如 GCC)提供的另一个特性引起的:它们将标准函数声明为所谓的弱符号,从而允许您用自己的定义“替换”它们。
这两个因素共同产生了您观察到的效果:弱符号替换::abs
也导致替换std::abs
. 这与语言标准的一致性是另一回事......无论如何,不要依赖这种行为 - 语言不能保证它。
在 GCC 中,可以通过以下简约示例重现此行为。一个源文件
#include <iostream>
void foo() __attribute__((weak));
void foo() { std::cout << "Hello!" << std::endl; }
另一个源文件
#include <iostream>
void foo();
namespace N { using ::foo; }
void foo() { std::cout << "Goodbye!" << std::endl; }
int main()
{
foo();
N::foo();
}
在这种情况下,您还将观察到第二个源文件中::foo
( "Goodbye!"
) 的新定义也会影响N::foo
. 两个调用都会输出"Goodbye!"
. 而且,如果您::foo
从第二个源文件中删除 的定义,则两个调用都将分派到::foo
和 output的“原始”定义"Hello!"
。
上述 20.5.1.2/4 给予的许可是为了简化<cmath>
. 允许实现简单地包含 C-style <math.h>
,然后重新声明函数std
并添加一些 C++ 特定的添加和调整。如果上面的解释正确地描述了问题的内部机制,那么它的主要部分取决于C 风格版本的函数的弱符号的可替换性。
请注意,如果我们在上面的程序中简单地全局替换int
为double
,代码(在 GCC 下)将表现“如预期” - 它会输出-5 5
. 发生这种情况是因为 C 标准库没有abs(double)
功能。通过声明我们自己的abs(double)
,我们不会替换任何东西。
但是,如果在从int
with切换后double
我们也从abs
to切换fabs
,那么原来的怪异行为将重新出现在它的全部荣耀中(输出-5 -5
)。
这与上面的解释是一致的。