声明可以影响std命名空间吗?

时间:2018-06-17 16:31:22

标签: c++ std reserved

#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;
}

我希望输出为-55,但输出为-5-5

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

是否与使用std或什么有关?

2 个答案:

答案 0 :(得分:90)

通过声明(和定义) global 命名空间中的标准函数然后将它们带入命名空间<cmath>来实现std的语言规范allows实现使用声明的手段。未指定是否使用此方法

  

20.5.1.2标题
   4 [...]然而,在C ++标准库中,声明(在C中定义为宏的名称除外)在命名空间{{1}的命名空间范围(6.3.6)内}}。这些名称是否未指定(包括任何重载   在第21至33条和附件D)中添加的内容首先在全局命名空间范围内声明,然后通过显式使用声明(10.3.3)注入到命名空间std中。

显然,您正在处理决定遵循此方法的实现之一(例如GCC)。即您的实施提供std,而::abs只是“引用”std::abs

在这种情况下仍然存在的一个问题是,为什么除了标准::abs之外,您还可以声明自己的::abs,即为什么没有多重定义错误。这可能是由某些实现(例如GCC)提供的另一个功能引起的:它们将标准函数声明为所谓的弱符号,从而允许您用自己的定义“替换”它们。

这两个因素共同创造了您观察到的效果:::abs的弱符号替换也会导致替换::abs。这与语言标准的一致性是一个不同的故事......无论如何,不​​要依赖于这种行为 - 语言无法保证这一点。

在GCC中,可以通过以下简约示例再现此行为。一个源文件

std::abs

另一个源文件

#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

上述20.5.1.2/4给出的许可是为了简化"Hello!"的实施。允许实现只包含C风格的<cmath>,然后重新声明<math.h>中的函数,并添加一些特定于C ++的添加和调整。如果上面的解释恰当地描述了问题的内在机制,那么它的主要部分取决于函数的 C风格版本的弱符号的可替换性。

请注意,如果我们在上述程序中仅使用std全局替换int,则代码(在GCC下)将“按预期”运行 - 它将输出double。发生这种情况是因为C标准库没有-5 5函数。通过声明我们自己的abs(double),我们不会替换任何内容。

但是,如果从abs(double)切换到int后我们也会从double切换到abs,原始奇怪的行为将重新出现在其完整的荣耀中(输出{{1 }})。

这与上述说明一致。

答案 1 :(得分:13)

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

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

  

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

因此,您无法使用与标准C库函数int abs(int);相同的原型创建函数。无论您实际包含哪些标头,或者这些标头是否也将C库名称放入全局名称空间。

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