为什么我的日志在std命名空间中?

71
在下面的代码中,我定义了一个简单的log函数。在main中,我试图调用它;我调用了std::log。然而,我的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;
}

12
听起来像是一个严重的编译器错误... - MFH
我可以在Macports下的g++ 4.6上重现这个问题。但在g++ 4.2或4.4中不会发生。 - carlosdc
1
从这两个链接中,我可以说std::log()函数调用了log()。但是它应该会生成一个错误/警告,指出您的文件重新定义了log或类似的内容。 - Gir
2
这很有趣。即使在ideone上也可以重现这个问题... http://ideone.com/wSPVF我也想知道这个答案。 - Constantinius
我在ideone中尝试了同样的操作 - 如果您注释掉新的日志函数,它将调用数学日志函数。 - Gir
5
这句话的意思是:使用命名空间的原因在于不是所有东西都需要放在类中。个人认为除了“main”之外的所有内容都应该放在命名空间中。 - GManNickG
5个回答

59
C++标准17.6.1.2第4段(重点在于“我”):
除了18到30条款和附录D中所述的内容外,每个头文件“cname”的内容应与相应的头文件“name.h”中的内容相同,如C标准库(1.2)或适当的C Unicode TR规定的那样,就好像已经包含一样。然而,在C ++标准库中,声明(除了在C中定义为宏的名称)位于命名空间std的命名空间范围内(3.3.6)。这些名称是否首先在全局命名空间范围内声明,然后通过显式的using-declarations(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++工具链没有提供任何错误消息。


“C++标准”指的是C++11标准(N3337);17.6.1.2的符号名称为“[headers]”,而17.6.4.3的符号名称为“[reserved.names]”。 - M.M

9
我猜测,std::log 只是简单地委托给了::log。不幸的是,::log 只提供了一个 float 重载,而您提供了一个 double 重载,使您的更匹配。但我仍然不明白它如何被考虑在重载集中。

需要使用<cmath>库来提供log(double)函数。 - aschepler
1
我本来期望在MSVC10上也会出现同样的情况,但是最终确实在链接时出现了重复符号错误,但只有在3或4次重建后才出现。 - Captain Obvlious
@aschepler: <cmath>是必须提供double std::log(double)的(我强调命名空间是因为它现在很重要)。标准要求它们在::std命名空间中,每个实现可以自由地将它们定义在全局命名空间中,如果他们选择这样做的话。 - Cornstalks
5
@DeadMGпјҡдҪ жҳҜдёҚжҳҜжҢҮCеә“еҮҪж•°::logжҸҗдҫӣдәҶdoubleеҸӮж•°зұ»еһӢпјҹиҖҢжҸҗдҫӣfloatзұ»еһӢзҡ„жҳҜ::logfгҖӮ - Cornstalks

9
在libstdc++的cmath中,您会看到这个:
using ::log;

因此,它从全局命名空间引入math.h函数到std中。不幸的是,您为double log(double)提供了一个实现,因此链接器将不使用math库中的实现。所以libstdc++中绝对存在一个错误
编辑:我认为这是libstdc++中的一个错误,因为当您明确要求使用std :: 版本时,std :: log 不应受到与C库的干扰。当然,这种覆盖标准库函数的方法是C语言的一种旧“特性”。
编辑2:我发现标准实际上并没有禁止从全局命名空间中引入名称到std中。因此并非错误,只是实现细节的结果。

“using”声明应该只导入在该点可见的声明,我认为这意味着稍后声明的函数不应以这种方式被导入。 - bames53
我编辑了我的回答,标准允许这种实现方式,首先声明math.h函数,然后将“double”版本引入std。 - DanielKO

6
在C++中,编译器可以在全局命名空间中实现C库并委托给它(这是实现定义的)。
引用块:
除了第18到30条和附录D中注明的内容外,每个头文件cname的内容应与相应的头文件name.h的内容相同,如C标准库(1.2)或C Unicode TR所指定的那样,在C++标准库中,声明(除了在C中定义为宏的名称)在namespace std的命名空间范围内(3.3.6)。然而,未指定这些名称是否首先在全局命名空间范围内声明,然后通过显式using-declarations注入到namespace std中(7.3.3)。
一般来说,我会避免使用与C标准库具有相同签名的函数。 C ++标准确实赋予编译器使用这些签名的自由,这意味着如果您尝试使用相同的签名,则可能会与编译器作斗争。因此,您会得到奇怪的结果。
我认为会出现链接器错误或警告,并且我认为值得报告此问题。
[编辑]
哇,快极了。

0

因为你在全局命名空间中覆盖了它。如果你不想转向更安全、更清洁的语言,比如Nim,使用命名空间可以避免这种危险。

命名空间演示的正确使用

#include <iostream>
#include <cmath>  // Uses ::log, which would be the log() here if it were not in a namespace, see https://dev59.com/Kmct5IYBdhLWcg3wsPd5

// 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接