语言规范允许实现通过以下方式实现<cmath>
:在全局名称空间中声明(并定义)标准函数,然后std
通过使用声明将它们引入名称空间。不确定是否使用这种方法
20.5.1.2标头
4 [...]但是,在C ++标准库中,声明(除了在C中定义为宏的名称)在名称空间的名称空间范围(6.3.6)之内std
。尚不确定这些名称(包括第21至33条和附件D中添加的任何重载)是否首先在全局名称空间范围内声明,然后std
通过显式using-声明(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
and 的“原始”定义"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
与切换之后,double
我们也从切换abs
至fabs
,原始的怪异行为将重新出现在其全部荣耀(输出-5 -5
)中。
这与以上解释一致。
abs
不正确。