在结构体/类声明内使用作用域Using指令?

29
我发现我的C++头文件非常难读(而且键入非常繁琐),因为它们包含了所有完全限定类型(深达4个嵌套命名空间)。这是问题(所有答案都给出了混乱的替代方法来实现它,但那不是问题):在C++语言中,在结构体和类中引入作用域using指令是否存在重要的理由(虽然在函数中允许使用声明)。例如:
class Foo : public Bar
{
    using namespace System;
    using namespace System::Network;
    using namespace System::Network::Win32::Sockets;
    using Bar::MemberFunc; // no conflict with this

    // e.g. of how messy my header files are without scoped using-directive
    void FooBar(System::Network::Win32::Sockets::Handle handle, System::Network::Win32::Sockets::Error& error /*, more fully-qualified param declarations... */);
};

由于namespace是一个关键字,我本以为它足够独特,不会与作用域限定的using声明(如Bar::MemberFunc)发生冲突。

编辑:请仔细阅读问题--->我已经将其加粗了。提醒:我们讨论如何改进示例的可读性。建议如何实现作用域限定的using指令(即通过添加关键字/结构等方式在C++语言中实现),不是一个答案(如果您能找到一种优雅的方法来使用现有的C++语言标准实现这个,那当然是一个答案)!


2
个人认为,当有太多嵌套的命名空间时,这是设计不良的迹象。 - Omnifarious
2
@Zach Saw - 这是糟糕的C++设计,不太好的Java设计,还有可以接受(但仍然不是很好)的Python设计。如果.NET框架用于C++,那么是的,它的设计不是很好。我认为C++确实需要像你建议的限制using声明作用域的机制。这将使得在C++中嵌套命名空间的设计比现在更好。 - Omnifarious
7
您可以将STL与更全面的库,如.NET和Java libs进行比较。如果C ++标准库更加广泛,我们肯定需要将其分解为更多的命名空间(当然还需要花费5年时间达成妥协)。但我们肯定需要某种形式的嵌套命名空间。个人认为这是一个好主意,但必须小心谨慎。 - Martin York
1
@Martin York - 我不认为嵌套的命名空间是一种永远不应该被应用于世界上的邪恶。Boost 使用它们很多。我只是觉得它们应该被节制地使用,你的嵌套深度应该保持较小,并且一个给定的命名空间应该有很多名称。目前 C++ 的工作方式使得大量嵌套的命名空间会引起问题,就像 OP 遇到的问题一样。 - Omnifarious
1
伙计们,问题是,“在语言中没有这个功能的强烈理由吗?” - Zach Saw
显示剩余4条评论
5个回答

14
有时我这样做几乎可以达到相同的效果:
namespace detail {
    using namespace System;
    using namespace System::Network;
    using namespace System::Network::Win32::Sockets;

    class Foo : public Bar
    {
         void FooBar(Handle handle, Error& error);
    };
}
using detail::Foo;

1
请仔细阅读问题后再回答。 - Zach Saw
6
我看到了。但这个问题有点无意义,因为它很可能没有答案。C++标准中有很多东西是没有意义的。 - Timo
3
有时,“为什么不允许使用X?”的答案是C ++ 提供了另一种实现相同功能的方式,就像这里所演示的那样。 - Brent Bradburn
这是我一直在努力解决的问题,不知道是否被认为是不好的实践。人们似乎说不要在头文件中使用 using namespace,但我想知道他们是否指的是全局空间。在声明使用大量向量的类之前,在自己的命名空间内在头文件中使用 using std:vector 是否可以? - thomthom
3
这会导致使用 Foo 的用户需要知道它在 detail 命名空间中,当 Foo 需要完全限定以解决歧义 (::Foo) 时会产生这种副作用。所以,不,这里展示的东西不能达到相同的效果。 - Zach Saw
1
@thomthom 在头文件中使用using指令的问题在于,包含该头文件的任何源文件,或者包含任何其他包含该头文件的文件的源文件,都会将这些using指令包含在其中,很可能甚至连源文件的编写者都不知道。如果它的范围受到限制,例如被放置在单独的命名空间中,则这个问题就不那么严重了。 - Justin Time - Reinstate Monica

12
考虑到在类作用域中使用的声明不会被继承,这种方法可能可行。该名称只在该类声明内或嵌套类的声明内有效。但我认为这有点过于重载类的概念并引入了一个更大的想法。
在Java和Python中,单独的文件是以特殊方式处理的。您可以有导入声明,将其他命名空间中的名称注入到文件中。这些名称(对于Python来说不完全是这样,但在此说明过于复杂)仅在该文件中可见。
对我来说,这表明了这种能力不应该与类声明绑定,而应该具有自己的作用域。这将允许在多个类声明中使用注入的名称,如果有意义的话,甚至可以在函数定义中使用。
以下是我更喜欢的一种方法,因为它既允许这些事情,同时又提供了类级别using声明的好处:
using {
   // A 'using' block is a sort of way to fence names in.  The only names
   // that escape the confines of a using block are names that are not
   // aliases for other things, not even for things that don't have names
   // of their own.  These are things like the declarations for new
   // classes, enums, structs, global functions or global variables.
   // New, non-alias names will be treated as if they were declared in
   // the scope in which the 'using' block appeared.

   using namespace ::std;
   using ::mynamespace::mytype_t;
   namespace mn = ::mynamespace;
   using ::mynamespace::myfunc;

   class AClass {
     public:
      AClass(const string &st, mytype_t me) : st_(st), me_(me) {
         myfunc(&me_);
      }

     private:
      const string st_;
      mn::mytype_t me_;
   };
// The effects of all typedefs, using declarations, and namespace
// aliases that were introduced at the level of this block go away
// here.  typedefs and using declarations inside of nested classes
// or namespace declarations do not go away.
} // end using.

// Legal because AClass is treated as having been declared in this
// scope.
AClass a("Fred", ::mynamespace::mytype_t(5));

// Not legal, alias mn no longer exists.
AClass b("Fred", mn::mytype_t);

// Not legal, the unqualified name myfunc no longer exists.
AClass c("Fred", myfunc(::mynamespace::mytype_t(5));

这类似于在函数中声明局部变量块。但在这种情况下,您正在声明一个非常有限的范围,以更改名称查找规则。

1
@Zach Saw - 我的观点是,一个不同但相关的特性实际上会更好、更清晰。 - Omnifarious
1
@Zach Saw - 我的意思是,我们不应该以你所说的那种方式来实现它,因为那样的范围太有限了(意图明确)。 - Omnifarious
1
@Zach Saw - 就我个人而言,我认为对于更普遍和/或更清晰的功能想法的存在是反对更有限和不太清晰的功能的完全正当的理由。 - Omnifarious
3
通常,标准委员会的工作方式相反。在语言中包含它是否有充分的理由?C++已经是一门庞大的语言。为了添加新特性,必须真正证明其成本合理。因此,此功能是否足够关键,以证明将语言变得更加庞大的行为是有道理的? - jalf
2
我对这个“using {}”的东西有些困惑。 这是一个功能想法,而不是当前有效的C++结构,对吗?@ZachSaw,如果这不是您要寻找的内容,为什么要接受这个答案呢? - Kyle Strand
显示剩余21条评论

1
你可以在类声明中使用typedef来实现相同的效果。
class Foo : public Bar
{
      typedef System::Network::Win32::Sockets::Handle Handle;
      typedef System::Network::Win32::Sockets::Error Error;

      void FooBar(Handle handle, Error& error);
};

2
在这种情况下,您最终将在类中使用的System::Network::Win32::Sockets命名空间下的所有类型进行typedef。不仅如此,您还必须对该类中使用的其他命名空间下的所有类型进行typedef!您意识到这是不切实际的,对吧?而且在CPP文件中,您仍然必须完全限定类型。 - Zach Saw
你当然是正确的。但这并不一定是件坏事。通常情况下,一个人不会想要(或者也不应该)使用完整的命名空间,而是特定的标识符(例如Handle)。而且,必须要做额外的打字有点麻烦,就像写"using System::Network::Win32::Sockets::Handle"但这并不意味着因为打字更少就应该更喜欢写""using System::Network::Win32::Sockets"。这是在细粒度控制/清晰度与更多文本之间的权衡。 - Declan
更正:最好写成“using System::Network::Win32::Sockets”… 在 .cpp 文件中通常可以使用整个命名空间(避免重复 typedef),因为它不会包含在其他头文件中并污染全局命名空间。 - Declan
不要假设一切都是程序员懒惰的问题。打更多字只是我最不关心的事情。在每个类中定义这么多类型会使代码变得非常冗长。可读性受到影响。 - Zach Saw
是的。当你不需要全部时,将所有内容放在命名空间“available”中会容易出错并污染当前命名空间。你只需要typedef你需要的标识符,通常不是很多。就像我说的那样,这是一种妥协。更多的文本意味着更多的控制,反之亦然。 - Declan

0

可能是命名空间别名?

namespace MyScope = System::Network::Win32::Sockets;

由于要为许多命名空间创建别名(加上无法限定别名的作用域),您很快就会遇到与全局使用命名空间相同的问题。 - Zach Saw
@Zach Saw - 你测试过确保它们不能被限制在范围内吗? - Omnifarious
MSVC2010不允许这样做。C++Builder2010也不行。 - Zach Saw
1
@Zach Saw - g++ 4.5.1允许将其限制在函数范围内。但它无法限制在类范围内。嗯...这并不是很有用。 - Omnifarious
那么没有编译器允许它。谁投票支持这个答案??? - Zach Saw
我认为你必须将嵌套的命名空间重新映射为一个扁平的、两层或三层的命名空间。 - nemethpeter

-2
命名空间的明显好处是可以避免命名冲突。然而,通过在类声明中引入整个命名空间,这种好处就被取消了。系统命名空间中的函数可能会与您自己的Bar::MemberFunc函数发生冲突。在您添加注释“没有冲突”时,甚至注意到了这一点。
显然,您不希望像这样将整个命名空间引入到您的类中:
using namespace System;
using namespace System::Network;
using namespace System::Network::Win32::Sockets;

你不能像这样在类声明中添加更具体范围的using语句。直接将它们放入类声明中是不合法的。

using System::Network::Win32::Sockets::Handle;
using System::Network::Win32::Sockets::Error;

你可以使用未命名的命名空间。因此,您的头文件将类似于这样:

namespace {
    using System::Network::Win32::Sockets::Handle;
    using System::Network::Win32::Sockets::Error;
}

class Foo : public Bar
{
    using Bar::MemberFunc;

    // clean!
    void FooBar(Handle handle, Error& error /*, more declarations*/);
};

这种方式有三个明显的优点。

  1. 不会引入整个命名空间的内容。这有助于更轻松地避免命名冲突。
  2. 在类声明之前,您可以列出类正确工作所依赖的内容列表。
  3. 函数声明更加清晰。

如果我错了,请纠正我;我只是简单地测试了一下,并快速编写了一个示例,然后它就编译通过了。


2
匿名命名空间仅限于翻译单元,而不是文件。这意味着匿名命名空间中的名称对于该翻译单元中的所有其他文件都可用,即使它们是其他头文件也是如此。这可能会悄悄地改变随机头文件的含义。 - Omnifarious
2
歧义通常可以按照惯例或者通过命名空间别名来解决。所以你的整个论点都是无效的。另外,你的未命名命名空间也没有范围限制 - 这非常糟糕。 - Zach Saw

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