使用命名空间行为背后的原理

5

引用自标准:

using-directive指定在using-directive出现之后的范围内可以使用所提名命名空间中的名称。在未经限定的名称查找期间(3.4.1),这些名称显示为在最近的封闭命名空间中声明,该命名空间包含了using-directive和所提名的命名空间。

看一下这段代码:

namespace A {

    int fn() { return 1; }

}

namespace Inner {

    int fn() { return 2; }

    namespace B {

        using namespace A;

        int z = fn();

    }

}

在我还不了解命名空间的确切规则之前,我曾期望在使用了using namespace A后,z将被初始化为1,因此期望使用A::fn()。但实际上不是这样的,由于我引用的规则,Inner::fn()被调用,z将被初始化为2。
这种行为背后的原理是什么呢?"似乎声明在最近的封闭命名空间中,该命名空间包含使用指令和所指定的命名空间"。
如果using namespace像应用于该命名空间中的所有内容一样工作,会有哪些问题呢?
注:这个链接与我提出这个问题有关。

1
这对我来说似乎也是违反直觉的,但看起来自 C++98 以来就一直是这样。 - aschepler
1个回答

5

命名空间系统的一个理想属性是我所谓的“增量API兼容性”。也就是说,如果我向命名空间添加一个符号,则任何先前可以工作的程序都应该继续工作并具有相同的含义。

现在,使用重载的普通C++不是“增量API兼容的”:

int foo(long x) { return 1; }

int main()
{
    foo(0);
}

现在我添加了重载int foo(int x) { return 2; },程序悄无声息地改变了意义。
总之,当C++人员设计namespace系统时,他们希望在增加外部API时,先前工作的代码不应更改选择符号的命名空间。从您的示例中,先前工作的代码将类似于:
namespace A {
    //no fn here, yet    
}

namespace Inner {

    int fn() { return 2; }

    namespace B {

        using namespace A;
        int z = fn();
    }
}

并且 z 可以轻松初始化为 2。现在,在命名空间 A 中添加一个名为 fn 的符号将不会改变该工作代码的含义。

相反的情况并不真正适用:

namespace A {
    int fn() { return 1; }
}

namespace Inner {

    // no fn here

    namespace B {

        using namespace A;
        int z = fn();
    }
}

这里z被初始化为1。当然,如果我将fn添加到Inner中,它会改变程序的含义,但是Inner不是一个外部API:事实上,当最初编写Inner时,A::fn已经存在(它正在被调用!),因此没有理由不知道冲突。

一个有些实际的例子

想象一下这个C++98程序:

#include <iostream>

namespace A {
int move = 0;
void foo()
{
    using namespace std;
    cout << move << endl;
    return 0;
}
}

int main()
{
    A::foo();
    return 0;
}

现在,如果我使用C++11编译这个程序,由于使用了using规则,一切正常。如果using namespace std作为应用该命名空间中所有内容的using声明起作用,那么这个程序将尝试打印函数std::move而不是A::move

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