即使在使用命名空间指令之后也没有模糊的引用错误

时间:2017-04-06 19:12:06

标签: c++ c++11 namespaces

以下代码生成call of overloaded ‘bar()’ is ambiguous错误应该是因为我在全局和bar命名空间中都有一个函数foo,我调用了using namespace foo指令。

namespace foo {
    void bar() {}
}

void bar() {}

using namespace foo;

int main() {
    bar();
}

我也期待以下代码出现同样的错误:

#include <cstdlib>
#include <iostream>

int abs(int n) {
    return n > 0 ? n : -n;
}

using namespace std;

int main() {
    int k;

    cin >> k;

    cout << abs(k) << endl;
}

我定义了一个函数int abs(int n),就像cstlib中存在的函数一样,我调用了using namespace std指令。所以应该像第一个例子一样出现错误。但没有。

我的问题是编译器如何解决这种歧义?在这种情况下会调用哪个函数,我或std的函数?这里有UB吗?

更新:从评论和答案来看,不同的编译器似乎表现不同。那么这个行为是未定义的还是实现定义的?

我已使用g++ 4.8.4Ubuntu 14.04上使用-std=c++11标记对其进行了测试。

[请注意,我确实理解using namespace std不好,我的abs功能并不比std更好或更差。我的问题不同。]

5 个答案:

答案 0 :(得分:6)

在C ++标准部分17.6.1 Library contents and organization中,我们在17.6.1.2中读到:

  

除第18条至第30条及附件D所述外,其内容为   每个标题cname应与相应的标题相同   标题name.h,如C标准库(1.2)或C中所指定   Unicode TR,视情况而定,就像包含一样。在C中   但是,++标准库,声明(在C中定义为宏的名称除外)在命名空间范围(3.3.6)内   命名空间标准。 未指明这些名称是否是第一个   在全局命名空间范围内声明,然后注入   命名空间std由显式使用声明(7.3.3)。

强调添加

此外,我们在17.6.4.3.2 External linkage中阅读了

  

使用外部链接声明的标准C库中的每个名称   保留给实现用作extern“C”的名称   在命名空间std和全局命名空间中的链接

在本节的简明英语中,类似的,C标准库名称是保留的,但C标准库名称仅在全局命名空间范围内。

GLIBCXX在这里做的是完全有效的;它在全局命名空间范围内声明abs并使用using-declarations将其注入std

的确,在我的系统/ g ++ 4.8.5和6.3.0使用的标准库中(我在coliru上检查过6.3.0),<cstdlib>看起来像这样:

// <stdlib.h>:

extern int abs (int __x) __THROW __attribute__ ((__const__)) __wur;
// <cstdlib>

#include <stdlib.h>

namespace std
{
    using ::abs;
}

using ::abs使std::abs调用你的函数。

您违反了ODR,因为GLIBC是一个共享库,它还提供了int abs(int)的实现。

你没有得到“abs(int)的多重定义”链接器错误可以说是编译器中的一个错误;如果他们警告这种不确定的行为会很好。

这可以通过这个例子再现:

<强>的main.cpp

#include <iostream>

int myabs(int);

namespace foo {
    int myabs(int n) {
        return ::myabs(n);
    }
}

int myabs(int n) {
    std::cout << "myabs inside main.cpp\n";
    return n > 0 ? n : -n;
}

using namespace foo;

int main() {
    int k = -1;

    std::cout << foo::myabs(k) << std::endl;
}

<强> myabs.cpp

#include <iostream>

int myabs(int n) {
    std::cout << "myabs inside myabs.cpp\n";
    return n > 0 ? n : -n;
}

然后在命令行上:

g++ -fPIC -c myabs.cpp
g++ -shared myabs.o -o libmyabs.so
g++ -L. main.cpp -lmyabs

正在运行./a.out调用 main.cpp 中定义的myabs,而如果您在 main.cpp中注释myabs ,它从 myabs.cpp

中调用一个

如何避免此问题

如果您避免在全局命名空间中声明函数,则应该主要避免此问题。

对于您的示例,如果我们改为写:

#include <cstdlib>
#include <iostream>

namespace {
    int abs(int n) {
        return n > 0 ? n : -n;
    }
}

using namespace std;

int main() {
    int k;

    cin >> k;

    cout << abs(k) << endl;
}

我们收到有关呼叫不明确的预期错误警告。但是,请注意,如果标准库在全局命名空间中声明abs,则无法解决问题:

int main() {
    int k;

    cin >> k;

    cout << ::abs(k) << endl;
}

这似乎只是调用标准库版本。当然,避免using namespace std

可以避免这个问题

答案 1 :(得分:5)

由于C头和C ++头之间的交互,问题是<cstdlib> is really complicated。在libstdc ++中,它没有实现为:

namespace std {
    int abs(int );
}

如果是这种情况,那么std::abs的示例程序与您对foo::bar的示例程序的期望相符,原因完全相同。但相反,它被声明为:

// from <stdlib.h>
extern int abs(int );

// from <cstdlib>
#include <stdlib.h>

namespace std {
    using ::abs;
}

当您声明并定义自己的::abs(int )时,这只是对先前声明的int ::abs(int )的重新声明。您没有超载任何内容 - 此翻译单元中只有一个int ::abs(int)!您可以看到,如果您尝试声明类似long abs(int )的内容 - 您会因为使用其他返回类型重新声明而收到错误。

这是有效的,因为C头中的::abs未定义(否则您将在重新定义时遇到编译错误) - 您通过共享库引入该定义。因此,您最终会遇到ODR违规,因为您在TU中定义了您在GLIBC中的定义和共享库定义,因此未定义行为。我不确定链接器为什么没有抓住它。

答案 2 :(得分:1)

如果以下列方式声明abs函数:

void abs(int n) { return n > 0 ? n : -n; } (返回类型已从int更改为void

这会引发error: ambiguating new declaration of 'void abs(int)'

因为在stdlib中它声明为int abs(int n),但我们现在使用其他返回类型定义它。

那么当我用正确的返回类型定义它时,为什么它没有抱怨?

首先,int abs(int k)的实现以编译形式(标准库)存在,而不是源代码形式。因此,如果已定义任何int abs(int k),则无法告知(在链接之前)。所以编译器很满意cstdlib中的声明和我们提供的源中的定义。当它开始链接时,它只搜索已声明但尚未定义的函数(以便它可以复制定义(假设链接对静态库))。因此,链接器不会搜索int abs(int k)的另一个定义。最后,我们给出的定义包含在结果二进制文件中。

答案 3 :(得分:-1)

我在<cstdlib>内注意到以下内容:

#ifndef __CORRECT_ISO_CPP_STDLIB_H_PROTO
  inline long
  abs(long __i) { return __builtin_labs(__i); }
  //...

当我使用long

尝试您的示例时
#include <cstdlib>
#include <iostream>

long abs(long n) {
    return n > 0 ? n : -n;
}

using namespace std;

int main() {
    long k;

    cin >> k;

    cout << abs(k) << endl;
}

我收到了预期的错误:

error: call of overloaded 'abs(long int&)' is ambiguous

也许你的实现正在做类似的事情。

答案 4 :(得分:-1)

让我们将此代码修改为:

#include <iostream>
#include <cstdlib>

int abs(int n) {
    std::cout << "default abs\n";
    return n > 0 ? n : -n;
}

//using namespace std;

int main() {
    int k;

    std::cin >> k;

    std::cout << std::abs(k) << std::endl;
}

它仍会呼唤你的腹肌。奇怪,是吧?好的,实际上std命名空间中没有int abs(int)函数。这里没有模糊的调用,具体取决于使用的平台,因为实际的abs定义为等于:

std::intmax_t abs( std::intmax_t n );

但实际执行情况可能会有所不同,具体取决于多种因素。 你所做的就是你有过载功能或模板。只要您没有达到头文件中的确切定义,如果它与参数匹配得更好,则将使用您的函数。如果全局使用std命名空间,则可以通过std模板而不是std :: abs()函数尝试候选。这是在全球范围内使用命名空间std的一个警告。

实际上,在我的系统std :: abs中定义为来自全局范围的abs:  当然,你有一个来自全局范围的函数和这样的原型,由你自己定义,所以在我的情况下std :: abs调用等于:: abs调用。

#include <iostream>
#include <cstdlib>

int abs( long n ) {
    std::cout << "default abs\n";
    return n > 0 ? n : -n;
}

//using namespace std;

int main() {
    int k;

    std::cin >> k;

    std::cout << std::abs(k) << std::endl;
}

现在它使用标准库函数并输出k的绝对值。

让我们看看cstdlib标头在特定情况下包含的内容:

_STD_BEGIN
using _CSTD size_t; using _CSTD div_t; using _CSTD ldiv_t;   
using _CSTD abort; using _CSTD abs; using _CSTD atexit;
// and so on..
_STD_END

_STD_BEGIN定义为

#define _STD_BEGIN  namespace std {

实际上我们有

namespace std {
     using ::abs;
}

这样在全局范围内获得标识符abs的任何内容都变为std :: abs 这得到了前向声明的强制,因此在此定义之后定义的abs()是主题。因为语言语法允许这样做,所以在全局范围内重新定义库标识符可能会导致格式错误的程序或UB,在这种情况下,归结为标题中的哪些声明处于活动状态。

  

C ++标准库保留以下类型的名称:

     
      
  •   
  • 全球名称
  •   
  • 具有外部链接的名称
  •   
     

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