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


69

在下面的代码中,我定义了一个平凡的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;
}

12
听起来像是一个严重的编译器错误……
MFH,2012年

我可以在Macports下的g ++ 4.6上重现它。不过,在g ++ 4.2或4.4中不会发生这种情况。
carlosdc

1
codepad.org/Uwhgrv7q codepad.org/z07Ctfyn Frome这两个我会说std :: log()函数调用log()。但随后它应该会生成错误/警告,提示您的文件重新定义了日志或类似t的
符号

2
这很有趣。我可以重现这个即使在ideone ... ideone.com/wSPVF 我想答案知道这个自己。
Constantinius

5
@ColeJohnson:这就是为什么使用命名空间的原因。并非所有内容都需要在课堂上。就个人而言,我将所有内容都放在命名空间中,除了main
GManNickG 2012年

Answers:


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尚不确定这些名称是先在全局名称空间范围内声明,然后std通过显式using-declaration(7.3.3)注入名称空间。

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]
MM

9

我希望发生的事情std::log就是将委托给::log。不幸的是,::log仅提供float重载,而您请提供double重载,使您的匹配更好。但是我仍然看不到它在过载集中如何被考虑。


<cmath>需要提供log(double)
aschepler,2012年

1
我期望在MSVC10上也能做到这一点,但最终确实在链接上出现重复的符号错误,但仅在3或4次重建后才出现。
船长Obvlious 2012年

@aschepler:<cmath>必须提供double std::log(double)(我现在非常关注名称空间,因为它是现在)。该标准要求它们在::std名称空间中,并且每个实现都可以自由选择是否在全局名称空间中对其进行定义。
2012年

5
@DeadMG:您的意思不是::log(C库函数)提供了double参数类型吗?它::logf提供的float类型。
2012年

9

在libstdc ++上,cmath您将看到以下内容:

using ::log;

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

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

编辑2:我发现该标准实际上并不禁止将名称从全局名称空间引入std。因此,毕竟不是错误,而仅仅是实现细节的结果。


using声明应该仅导入在这一点上是可见的声明,我认为,这将意味着一个后来被宣布功能应该没有导入这种方式。
bames53

我编辑了答案,该标准允许这种实现,首先声明了math.h函数,然后将double版本引入std
DanielKO 2012年

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 ++标准肯定会给编译器提供使用这些签名的自由,如果它选择的话,这意味着如果您尝试使用相同的签名,则可能会与您的编译器抗争。因此,您得到奇怪的结果。

我希望有链接器错误或警告,但我认为可能值得报告。

[编辑]

哇,忍者


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