70

在下面的代码中,我定义了一个简单的log函数。在main我尽量叫它;我打电话std::log。然而,我自己的log被称为;我看到“日志!” 在屏幕上。有谁知道为什么?我使用 G++ 4.7 和 clang++ 3.2。

#include <iostream>
#include <cmath>

double log(const double x) { std::cout << "log!\n"; return x; }

int main(int argc, char *argv[])
{
  std::log(3.14);
  return 0;
}
4

5 回答 5

58

C++ 标准 17.6.1.2 第 4 段(强调我的):

除第 18 至 30 条和附录 D 中另有说明外,每个标题的内容cname应与相应标题的内容相同name.h,如 C 标准库 (1.2) 或 C Unicode TR 中规定的,视情况而定,如同通过包容。但是,在 C++ 标准库中,声明(在 C 中定义为宏的名称除外)在 namespace 的命名空间范围 (3.3.6) 内std未指定这些名称是否首先在全局命名空间范围内声明,然后通过显式使用声明(7.3.3)注入命名空间。std

g++ 采用后一种方式,这样一些相同的头文件可以被 C 和 C++ 重用。所以允许g++double log(double)在全局命名空间中声明和定义。

第 17.6.4.3.3 节第 3 和第 4 段:

使用外部链接声明的标准 C 库中的每个名称都保留给实现,以在命名空间和全局命名空间中用作具有extern "C"链接的名称。std

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

在第 17.6.4.3 节第 2 段的顶部:

如果程序在保留名称的上下文中声明或定义名称,除非本条款明确允许,否则其行为未定义。

另一方面,您不得以任何方式声明或定义::log

不过,g++ 工具链没有给您任何错误消息,这太糟糕了。

于 2012-08-09T23:02:46.230 回答
9

我预计会发生什么,std::log只是将::log. 不幸的是,::log只提供了一个float重载,而您请提供一个double重载,使您的匹配更好。但是我仍然看不到它在重载集中是如何被考虑的。

于 2012-08-09T22:46:28.593 回答
9

在 libstdc++ 上,cmath您将看到:

using ::log;

所以它将 math.h 函数从全局命名空间引入到std. 不幸的是,您正在为 提供一个实现double log(double),因此链接器不会使用数学库中的那个。所以绝对是 libstdc++ 中的一个错误

编辑:我声称这是 libstdc++ 中的一个错误,因为当您明确要求版本std::log时,不应受到与 C 库的干扰。std::当然,这种覆盖标准库函数的方式是 C 语言的一个旧“特性”。

编辑 2:我发现该标准实际上并没有禁止将名称从全局命名空间带入std. 所以毕竟不是错误,只是实现细节的结果。

于 2012-08-09T22:47:21.983 回答
6

在 C++ 中,编译器可以自由地在全局命名空间中实现 C 库并委托给它(这是实现定义的)。

17.6.1.2.4 除了第 18 到 30 条和附录 D 中的说明外,每个头文件 cname 的内容应与 C 标准库 (1.2) 或 C 中指定的相应头文件 name.h 的内容相同。 Unicode TR,视情况而定,好像通过包含。然而,在 C++ 标准库中,声明(在 C 中定义为宏的名称除外)在命名空间 std 的命名空间范围 (3.3.6) 内。未指定这些名称是否首先在全局命名空间范围内声明,然后通过显式 using 声明(7.3.3)注入命名空间 std 。

一般来说,我会避免使用与 C 标准库之一具有相同签名的函数。C++ 标准当然让编译器可以自由选择使用这些签名,这意味着如果您尝试使用相同的签名,您可能会与编译器发生冲突。因此,您会得到奇怪的结果。

不过,我预计会出现链接器错误或警告,我认为值得报告这一点。

[编辑]

哇,忍者。

于 2012-08-09T23:06:29.897 回答
0

因为您已经在全局命名空间中覆盖了它。如果您不想继续使用像Nim这样的更安全、更清洁的语言,则使用命名空间可以避免这种危险。

正确使用命名空间演示

#include <iostream>
#include <cmath>  // Uses ::log, which would be the log() here if it were not in a namespace, see http://stackoverflow.com/questions/11892976/why-is-my-log-in-the-std-namespace

// Silently overrides std::log
//double log(double d) { return 420; }

namespace uniquename {
    using namespace std;  // So we don't have to waste space on std:: when not needed.

    double log(double d) {
        return 42;
    }

    int main() {
        cout << "Our log: " << log(4.2) << endl;
        cout << "Standard log: " << std::log(4.2);
        return 0;
    }
}

// Global wrapper for our contained code.
int main() {
    return uniquename::main();
}

输出:

Our log: 42
Standard log: 1.43508
于 2015-05-06T01:51:41.337 回答