为什么在类/结构体级别上不允许使用"using namespace X;"?

110
class C {
  using namespace std;  // error
};
namespace N {
  using namespace std; // ok
}
int main () {
  using namespace std; // ok
}

我想了解背后的动机。


1
@pst:C# 没有类似 using namespace 的语法。C# 允许在文件范围内进行类似的操作。而 C++ 的 using namespace 则允许将一个命名空间嵌入到另一个命名空间中。 - Billy ONeal
2
这是一个关于编程的问题,请参考此链接:https://dev59.com/5m855IYBdhLWcg3wZzbf。 - greatwolf
6个回答

39
我不确定,但我的猜测是,在类范围内允许这样做可能会引起混淆:
namespace Hello
{
    typedef int World;
}

class Blah
{
    using namespace Hello;
public:
    World DoSomething();
}

//Should this be just World or Hello::World ?
World Blah::DoSomething()
{
    //Is the using namespace valid in here?
}

由于没有明显的方法可以做到这一点,标准只是说你不能这样做。

现在,当我们谈论命名空间作用域时,这个问题就不那么令人困惑了:

namespace Hello
{
    typedef int World;
}

namespace Other
{
    using namespace Hello;
    World DoSomething();
}

//We are outside of any namespace, so we have to fully qualify everything. Therefore either of these are correct:

//Hello was imported into Other, so everything that was in Hello is also in Other. Therefore this is okay:
Other::World Other::DoSomething()
{
    //We're outside of a namespace; obviously the using namespace doesn't apply here.
    //EDIT: Apparently I was wrong about that... see comments. 
}

//The original type was Hello::World, so this is okay too.
Hello::World Other::DoSomething()
{
    //Ditto
}

namespace Other
{
    //namespace Hello has been imported into Other, and we are inside Other, so therefore we never need to qualify anything from Hello.
    //Therefore this is unambiguiously right
    World DoSomething()
    {
        //We're inside the namespace, obviously the using namespace does apply here.
    }
}

6
+1,我想到了这个理由,但是同样适用于在其他命名空间中使用 namespace Hello; (和在其中声明 extern 函数)。 - iammilind
12
我认为这并不令人困惑。C++编程不是猜测游戏。如果允许这样做,C++ ISO委员会就会在语言规范中指定。那么你就不会说这很困惑了。否则,有人可能会说甚至这个也很困惑:http://www.ideone.com/npOeD ...但是这种编码规则已经在规范中指定。 - Nawaz
7
在第一个例子中,应该写成:Hello::World Blah::DoSomething() 或者 Blah::World Blah::DoSomething()(如果允许的话)。在语言中,成员函数定义的返回类型不被视为类的作用域,因此必须进行限定。考虑使用在类作用域下替换 usingtypedef Hello::World World; 的有效示例。所以这里不应该有任何意外情况。 - David Rodríguez - dribeas
2
如果允许的话,我相信它将应用于词法作用域级别。我认为这是“显而易见”的解决方案,几乎没有什么意外。 - Thomas Eding
1
这就像在类内部进行typedef一样。如果你在MyClass类中typedef int MyNumber,那么你必须写成MyClass::MyNumber MyClass::getNumber() {...}。但是,在类内部进行typedef是允许的,而命名空间却不行?这很不合理。 - Youda008
显示剩余9条评论

14
由于C++标准明确禁止。来自C++03 §7.3.4 [namespace.udir]: using-directive: using namespace ::opt nested-name-specifieropt namespace-name ;

using-directive不得出现在类作用域中,但可以出现在命名空间作用域或块作用域中。[注意:在使用using指令中查找命名空间名称时,仅考虑命名空间名称,详见3.4.6。]

为什么C++标准会禁止呢?我不知道,请询问批准该语言标准的ISO委员会成员。

78
又是一个技术上正确但毫无用处的回答,最糟糕的一种。1)除了委员会以外,还有更多人知道答案。2)委员会成员参与了SO。3)如果你不知道答案(考虑到问题的本质),为什么要回答呢? - Catskul
12
@Catskul说的不是无用的答案。知道标准明确地处理了这个问题并禁止它是非常有用的。最赞的回答开头说“我不确定”,这也很讽刺。此外,“标准不允许”与“不允许是因为编译器不允许”并不相同,因为后一种情况不能回答后续问题,例如:“是我的编译器出了问题吗?”,“编译器是否符合标准?”,“这是我不知道的其他事情的副作用吗?”等等。 - antonone
1
虽然这不是“最糟糕的”答案,但它并没有回答问题:为什么C++禁止它。OP已经确定了C++禁止它。 - Zendel
1
@antonone 这个问题承认目前是被禁止的,并明确要求解释其背后的动机。因此,这个答案是不充分的,没有回答问题。 - Andrey Semashev
@AndreySemashev 这个问题承认“被禁止”,但它仍然可能被编译器实现所禁止。这个答案明确指出它是被标准所禁止的。这有助于集中精力进一步搜索更精确的答案。在这个领域问题的歧义让我认为问题出在问题本身,而不是答案上。 - antonone

13
我认为这样做可能会令人困惑。目前,在处理类级别标识符时,查找将首先在类作用域中进行,然后在封闭的命名空间中进行。在类级别允许使用 using namespace 将对现有查找方式产生相当多的副作用。特别是,它必须在检查特定类作用域和检查封闭命名空间之间的某个时刻执行。也就是说:1)合并类级别和已使用命名空间级别的查找,2)在检查任何其他类作用域之前查找已使用的命名空间 ,3)在检查封闭命名空间之前查找已使用的命名空间,4)与封闭命名空间合并查找。
这将产生很大的差异,其中类级别的标识符将遮蔽封闭命名空间中的任何标识符,但不会遮蔽一个已使用的命名空间。其效果会很奇怪,因为从不同命名空间的类访问已使用的命名空间与从相同命名空间的类访问会有所不同:
namespace A {
   void foo() {}
   struct B {
      struct foo {};
      void f() {
         foo();      // value initialize a A::B::foo object (current behavior)
      }
   };
}
struct C {
   using namespace A;
   struct foo {};
   void f() {
      foo();         // call A::foo
   }
};
  1. 在类作用域之后查找。这将产生一个奇怪的效果,即遮盖基类成员。当前的查找不会混合类和命名空间级别的查找,在执行类查找时,它会一直到基类之前考虑封闭命名空间。这种行为是令人惊讶的,因为它不会像封闭命名空间那样考虑相似级别的命名空间。同样,使用的命名空间将优先于封闭命名空间。

.

namespace A {
   void foo() {}
}
void bar() {}
struct base {
   void foo();
   void bar();
};
struct test : base {
   using namespace A;
   void f() {
      foo();           // A::foo()
      bar();           // base::bar()
   }
};
  1. 在封闭命名空间之前查找。这种方法的问题是,对许多人来说会感到惊讶。考虑命名空间是在不同的翻译单元中定义的,因此以下代码不能一次性看到:

.

namespace A {
   void foo( int ) { std::cout << "int"; }
}
void foo( double ) { std::cout << "double"; }
struct test {
   using namespace A;
   void f() {
      foo( 5.0 );          // would print "int" if A is checked *before* the
                           // enclosing namespace
   }
};
  1. 与封闭命名空间合并。这将产生与在命名空间级别应用using声明完全相同的效果。它不会为此添加任何新值,但另一方面会使编译器实现者查找变得复杂。命名空间标识符查找现在独立于触发查找的代码位置。当在类内部时,如果查找在类作用域中找不到标识符,则会回退到命名空间查找,但这正是在函数定义中使用的相同命名空间查找,没有必要维护新状态。当在命名空间级别找到using声明时,使用的命名空间的内容将被引入该命名空间以便于涉及该命名空间的所有查找。如果允许在类级别使用using namespace,则对于完全相同命名空间的命名空间查找,取决于查找从哪里触发,将有不同的结果,这将使查找的实现变得更加复杂而没有额外的价值。

无论如何,我的建议是使用using namespace声明。这样可以使代码更简单易懂而无需记住所有命名空间的内容。


1
我同意使用using会产生一些隐含的奇怪问题。但是有些库可能会围绕着using存在而设计。例如,通过在深度嵌套的长命名空间中故意声明事物。glm就是这样做的,并使用多种技巧在客户端使用using时激活/呈现功能。 - v.oddou
即使在STL中也可以使用 using namespace std::placeholders。请参考 http://en.cppreference.com/w/cpp/utility/functional/bind - v.oddou
@v.oddou:namespace ph = std::placeholders; - David Rodríguez - dribeas

6

这可能会被禁止,原因是 开放性封闭性 的对立。

  • C++ 中的类和结构体始终是封闭的实体。它们只能在一个地方定义(虽然可以分开声明和实现)。
  • 命名空间可以任意打开、重新打开和扩展。

将命名空间引入类会导致有趣的情况,比如:

namespace Foo {}

struct Bar { using namespace Foo; };

namespace Foo {
using Baz = int; // I've just extended `Bar` with a type alias!
void baz(); // I've just extended `Bar` with what looks like a static function!
// etc.
}

4
或者我们可以不使用导入的名称来定义类成员。让这个结构体添加namespace Foo到所有代码的搜索顺序中,就像在每个内联成员函数体中放置该行一样,但它也会对大括号或等于初始化器等起作用。但它仍然会在闭合大括号时过期,就像成员函数体内的using namespace一样。现在,不幸的是,在不污染封闭命名空间的情况下,在大括号或等于初始化器中使用Koenig-with-fallback查找似乎没有任何方法。 - Ben Voigt
我不明白为什么这是个问题。你可以在只定义一次的函数中使用using namespace(我知道内联有点绕过了这个问题,但这里并不重要),但在类中却不能这样做。 - Hrvoje Jurić
@HrvojeJurić 因为函数不会重新导出任何名称。类/结构体会这样做。 - Daniel Steck
@AndreySemashev 没有明确定义的概念来定义结构体定义的“之前/之后”。想象一下多个翻译单元(TUs),每个翻译单元都与一个定义所涉及的头文件一起导入不同的头文件。 - Daniel Steck
@DanielSteck > 没有明确定义什么是在结构体定义之前/之后定义的概念。-- 是的,但关键是一旦定义了结构体,它就被定义了,而且只有在它使用的名称(例如类型和函数)已经在之前定义时,定义才有效。一旦定义了结构体,任何未来的定义都不会对其产生影响。 - Andrey Semashev
显示剩余3条评论

4

我认为这是语言的缺陷。您可以使用以下解决方法。谨记此解决方法,可以轻松地针对语言更改时的名称冲突解决提出规则。

namespace Hello
{
    typedef int World;
}
// surround the class (where we want to use namespace Hello)
// by auxiliary namespace (but don't use anonymous namespaces in h-files)
namespace Blah_namesp {
using namespace Hello;

class Blah
{
public:
    World DoSomething1();
    World DoSomething2();
    World DoSomething3();
};

World Blah::DoSomething1()
{
}

} // namespace Blah_namesp

// "extract" class from auxiliary namespace
using Blah_namesp::Blah;

Hello::World Blah::DoSomething2()
{
}
auto Blah::DoSomething3() -> World
{
}

你能否添加一些解释? - Kishan Bharda
是的,我已经添加了一些注释。 - naprimeroleg
这很好,但请注意,在涉及该类的错误消息中,辅助命名空间的名称会出现。 - Boann

-2

你不能在类内使用 using namespace,但是你可以简单地使用 #define,然后在结构体内使用 #undef。它将与 namespace a = b; 完全相同。

struct foo
{
#define new_namespace old_namespace
     
    void foo2()
    {
        new_namespace::do_something();
    }

#undef new_namespace 
};

7
虽然你可以这样做,但如果我在进行代码审查,我绝不会让它通过。 - HolyBlackCat
2
我知道这不是好的做法,但这已经是最好的了。 - SimpleY

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