【问题标题】:Why is "using namespace X;" not allowed at class/struct level?为什么是“使用命名空间 X;”不允许在类/结构级别?
【发布时间】:2011-09-13 16:25:13
【问题描述】:
class C {
  using namespace std;  // error
};
namespace N {
  using namespace std; // ok
}
int main () {
  using namespace std; // ok
}

我想知道背后的动机。

【问题讨论】:

  • @pst:C# 没有类似using namespace 的东西。 C# 允许类似的东西,但仅在文件范围内。 C++ 的using namespace 允许您将一个命名空间合并到另一个命名空间中。
  • this question 的重复?
  • @ZachSaw,我理解你的担忧。已尝试根据相关性关闭 Qn。由于这篇文章包含更客观的答案和对标准的参考,我一直保持开放状态。过去,我的许多旧 Qn 都被新 Qn 关闭..有时被我有时被其他人关闭。如果您认为此决定不合适,请向钻石 Mods 举报。别往心里放。 :-)
  • @iammilind 不在乎 TBH。所以这些天是一团糟。但是,将以“我不完全知道”开头的帖子标记为答案确实包含“更客观的答案和对标准的参考”。哈哈。
  • @ZachSaw,我说的不仅仅是接受的答案,而是整个帖子。是的,这是客观的,但标准报价包含在this answer 中。它以“我不知道”开头,因为即使在标准中,为什么在 class/struct 中不允许“使用命名空间”也是不合理的。只是根本不允许。但是接受的答案确实讨论了一个非常合乎逻辑的理由来禁止它。即在哪里考虑Hello::World 和在哪里考虑World。希望能消除疑虑。

标签: c++ namespaces using language-lawyer


【解决方案1】:

我不确切知道,但我的猜测是在类范围内允许这样做可能会导致混淆:

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

【讨论】:

  • +1,我想到了这个原因,但是同样的事情也适用于其他namespace中的using namespace Hello;(并在其中声明extern函数)。
  • 我不认为它令人困惑。 C++ 与猜测无关。如果允许,那么 C++ ISO 委员会将在语言规范中指定。那你就不会说它令人困惑。否则有人可能会说即使这也令人困惑:ideone.com/npOeD ... 但是在规范中指定了这种编码的规则。
  • @Nawaz:该语言的大多数用户。我从来没有说过 C++ 是关于猜测的。我的意思是,在设计规范时,它是按照大多数程序员提前预期的行为设计的。而且纸上的规则常常令人困惑——标准试图明确,但并不总是成功。
  • 在第一个例子中,应该是:Hello::World Blah::DoSomething()Blah::World Blah::DoSomething()(如果允许的话),成员函数定义的返回类型不被认为是在类的范围内在语言中,所以它必须是合格的。考虑在类范围内用typedef Hello::World World; 替换using 的有效示例。所以那里应该没有惊喜。
  • 如果允许的话,我相信它会被应用到词法范围级别。我认为这是一个“显而易见”的解决方案,几乎没有任何意外。
【解决方案2】:

因为 C++ 标准明确禁止它。来自 C++03 §7.3.4 [namespace.udir]:

使用指令: 使用命名空间 ::optnested-name-specifieroptnamespace-name

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

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

【讨论】:

  • 又一个技术上正确但无用的答案;最差的那种。 1)不仅仅是委员会知道答案的人更多。 2) 委员会成员参与 SO 3) 如果您不知道答案(考虑到问题的精神),为什么要回答?
  • @Catskul:这不是一个无用的答案。知道该标准明确解决并禁止它是非常有用的。具有讽刺意味的是,最受好评的答案以“我不完全知道”开头。此外,“标准禁止”与“不允许,因为编译器不允许”不同,因为后一种情况不会回答后续问题,例如:我的编译器有问题吗?编译器不符合标准吗?这是我不知道的其他一些事情的副作用吗?等
【解决方案3】:

我相信这样做的理由是它可能会令人困惑。目前,在处理类级别标识符时,查找将首先在类范围内搜索,然后在封闭的命名空间中搜索。在类级别允许using namespace 将对现在如何执行查找产生相当多的副作用。特别是,它必须在检查特定类范围和检查封闭命名空间之间的某个时间执行。即:1) 合并类级别和使用的命名空间级别查找,2) 在类范围之后但在任何其他类范围之前查找使用的命名空间,3) 在封闭命名空间之前查找使用的命名空间. 4) 查找与封闭命名空间合并。

  1. 这将产生很大的不同,其中类级别的标识符将 shadow 封闭命名空间中的任何标识符,但它不会 shadow 一个 used 命名空间。效果会很奇怪,因为从不同命名空间中的类和从同一命名空间访问 used 命名空间会有所不同:

.

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. 在此类范围之后查找。这会产生隐藏基类成员的奇怪效果。当前查找不混合类和命名空间级别的查找,并且在执行类查找时,考虑到封闭的命名空间,它将一直到基类before。这种行为会令人惊讶,因为它不会考虑与封闭命名空间相似级别的命名空间。同样,used 命名空间将优先于封闭的命名空间。

.

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 声明时,used 命名空间的内容被带入 到该命名空间,以进行涉及该命名空间的所有 查找.如果在类级别允许using namespace,则对于完全相同的命名空间的命名空间查找将有不同的结果,具体取决于触发查找的位置,这将使查找的实现更加复杂而没有额外的价值。

无论如何,我的建议是使用using namespace 声明。它使代码更易于推理,而无需记住所有命名空间的内容。

【讨论】:

  • 我同意使用往往会产生隐含的古怪。但是有些库可能是围绕using 存在这一事实而设计的。通过故意在深层嵌套的长命名空间中声明事物。例如。 glm 会这样做,并在客户端使用 using 时使用多种技巧来激活/呈现功能。
  • 甚至在 STL using namespace std::placeholders 中。 c.f en.cppreference.com/w/cpp/utility/functional/bind
  • @v.oddou: namespace ph = std::placeholders;
【解决方案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
{
}

【讨论】:

  • 你能补充一些解释吗?
  • 是的,我添加了一些 cmets
  • 这很好,但请注意辅助命名空间的名称出现在涉及类的错误消息中。
【解决方案5】:

这可能是不允许的,因为 opennessclosedness。

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

【讨论】:

  • 或者我们不能用导入的名称定义类成员。让这个构造将 namespace Foo 添加到 struct Bar 的类型定义内的所有代码的搜索顺序中,就像将该行放在每个内联成员函数体中一样,除了它对于大括号或等号初始化器也是有效的,等等,但它仍然会在右大括号处过期,与成员函数体内的using namespace 相同。现在不幸的是,似乎没有任何方法可以在不污染封闭命名空间的情况下在大括号或相等初始化程序中使用 Koenig-with-fallback 查找。
  • 我不明白为什么这是个问题。您可以在只定义一次的函数中使用 using namespace(我知道内联有点绕过这一点,但这在这里并不重要),但您不能在类中使用。
  • @HrvojeJurić 因为函数不会(重新)导出任何名称。类/结构可以。
【解决方案6】:

您不能在类中使用using namespace,但您可以简单地使用#define,然后在结构中使用#undef。它的行为方式与namespace a = b;

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

#undef new_namespace 
};

【讨论】:

  • 虽然您可以,但如果我在进行代码审查,我绝不会让这件事通过。
  • 我知道这不是一个好习惯,但它已经很好了
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-19
  • 1970-01-01
  • 2010-11-15
  • 2011-04-28
  • 1970-01-01
相关资源
最近更新 更多