声明会影响std名称空间吗?


96
#include <iostream>
#include <cmath>

/* Intentionally incorrect abs() which seems to override std::abs() */
int abs(int a) {
    return a > 0? -a : a;
}

int main() {
    int a = abs(-5);
    int b = std::abs(-5);
    std::cout<< a << std::endl << b << std::endl;
    return 0;
}

我希望输出将是-5and 5,但是输出是-5and -5

我不知道为什么会发生这种情况?

它与的使用有关std吗?


1
您对的实现abs不正确。
理查德·克里顿

31
@RichardCritten这就是重点。OP询问为什么添加此损坏的abs影响std::abs()
HolyBlackCat

11
有趣的是,我得到55铿锵,-5-5与海湾合作委员会。
Rakete1111

10
Cmake不是编译器,而是构建系统。您可以使用cmake与各种编译器一起构建。
HolyBlackCat

5
我可能会建议您只使用函数return 0-这样可以避免让人们认为您无意间错误地实现了该函数,并使所需的行为和实际行为更清晰。
Bernhard Barker

Answers:


92

语言规范允许实现通过以下方式实现<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从第二个源文件中删除的定义,则这两个调用都将分派到::fooand 的“原始”定义"Hello!"


上面20.5.1.2/4给出的许可可以简化的实现<cmath>。允许实现仅包含C-style <math.h>,然后重新声明其中的功能std并添加一些特定于C ++的添加和调整。如果上面的解释正确地描述了问题的内部机制,那么它的主要部分取决于函数的C样式版本的弱符号的可替换性。

请注意,如果我们在上述程序中简单地全局替换intdouble,则代码(在GCC下)将表现为“预期”-将输出-5 5。发生这种情况是因为C标准库没有abs(double)功能。通过声明我们自己的abs(double),我们不会替代任何东西。

但是,如果从int与切换之后,double我们也从切换absfabs,原始的怪异行为将重新出现在其全部荣耀(输出-5 -5)中。

这与以上解释一致。


正如我在cmath的源代码中看到的那样,没有using ::abs;这样的东西,using ::asin;因此可以覆盖该声明,还有一点要提到的是,std名称空间函数中定义的不是声明为int而是声明为doublefloat
Take_Care_

2
从标准的角度来看,该行为未按[extern.names] / 4定义
xskxzr

但是,当我删除了#include<cmath>在我的代码,我得到了同样的answer.`
彼得·

@Peter但是你从哪里得到std :: abs?-可能已通过另一个包含项将其包含进来,此时您将回到该说明。(对于标头是直接包含还是间接包含无关紧要的编译器。)
RM

@Peter:abs也可以在中声明<cstdlib>,可以通过隐式包含<iostream>。尝试删除自己的文件abs,看看它是否仍然可以编译。
AnT

13

您的代码导致未定义的行为。

C ++ 17 [extern.names] / 4:

使用外部链接声明的C标准库中的每个函数签名均保留给实现,以用作具有extern“ C”和extern“ C ++”链接的函数签名,或用作全局命名空间中的命名空间范围的名称。

因此,您不能使用与标准C库函数相同的原型创建函数int abs(int);。不管您实际包括哪个标头,或者这些标头是否还将C库名称都放入全局命名空间中。

但是,abs如果您提供不同的参数类型,则将允许重载。


1
“或作为全局名称空间中名称空间作用域的名称”,因此它不能在全局名称空间中重载。
xskxzr

@xskxzr我不确定您引用的文本的解释;如果认为这意味着用户无法在全局名称空间中声明该名称的任何内容,那么我引用的文本的前一部分将是多余的,就像大多数[extern.names] / 3一样。这使我认为这里还有其他意图。
MM
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.