struct/class 声明中的 scoped using 指令?[复制]

Scoped using-directive within a struct/class declaration? [duplicate]

提问人:Zach Saw 提问时间:12/6/2010 最后编辑:taskinoorZach Saw 更新时间:5/7/2017 访问量:31786

问:

我发现我的 C++ 头文件很难阅读(而且键入起来非常乏味),所有完全限定的类型(深入到 4 个嵌套命名空间)。这就是问题所在(所有答案都给出了实现它的混乱替代方案,但这不是问题):是否有充分的理由反对在 C++ 语言的结构和类中引入作用域 using-directive(虽然允许在函数中使用作用域 using-declaration)?

例如:

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... */);
};

由于是一个关键字,我认为它足够独特,不会与作用域内的使用声明发生冲突,例如 .namespaceBar::MemberFunc

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

C++ 语言特性 using 指令

评论

2赞 Omnifarious 12/6/2010
就个人而言,我认为当有这么多嵌套命名空间时,这是设计不佳的标志。
2赞 Omnifarious 12/6/2010
@Zach Saw - C++设计很糟糕,Java设计不是很好,Python设计还可以(但仍然不是很好)。如果 C++ 的 .NET 框架做到了这一点,那么是的,它的设计不是很好。我确实认为 C++ 可以使用您建议的机制来限制使用声明的范围。这将导致大量嵌套的命名空间在 C++ 中的设计不如现在那么糟糕。
7赞 Martin York 12/6/2010
@Omnifarious:您可以将 STL 与更广泛的库(如 .NET 和 Java 库)进行比较。如果 C++ 标准库更广泛,我们肯定需要将其分解为更多的命名空间(当然,要花 5 年时间来妥协它是什么)。但是我们肯定需要某种形式的嵌套命名空间。我个人认为这是一个好主意,但你必须小心。
1赞 Omnifarious 12/6/2010
@Martin York - 我不认为嵌套命名空间是一种永远不应该出现在世界上的邪恶。Boost 经常使用它们。我只是认为应该谨慎使用它们,并且您的嵌套深度应该保持较小,并且给定的命名空间应该有很多名称。C++ 目前的工作方式使得广泛嵌套的命名空间会导致问题,就像 OP 遇到的问题一样。
1赞 Zach Saw 12/6/2010
伙计们,问题是,“有没有充分的理由在语言中没有这个功能?

答:

0赞 nemethpeter 12/6/2010 #1

也许是命名空间别名?

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

评论

0赞 Zach Saw 12/6/2010
由于有如此多的命名空间要别名(再加上无法确定别名的作用域),您很快就会遇到与全局使用命名空间相同的问题。
0赞 Omnifarious 12/6/2010
@Zach Saw - 您是否进行了测试以确保它们不能被限制在范围内?
0赞 Zach Saw 12/6/2010
MSVC2010不允许这样做。C++ Builder2010 也没有。
1赞 Omnifarious 12/6/2010
@Zach Saw - g++ 4.5.1 允许将其限制在函数范围内。但它不能局限于类范围。嗯。。。那没那么有用。
0赞 Zach Saw 12/6/2010
所以没有编译器允许它。谁投了这个答案???
14赞 Timo 12/6/2010 #2

有时我这样做是为了达到几乎相同的效果:

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 12/6/2010
在回答之前,请仔细阅读问题。
6赞 Timo 12/6/2010
我看到了。但这个问题有点毫无意义,因为它很可能没有答案。C++ 标准中的很多东西都没有意义。
3赞 Brent Bradburn 11/7/2013
有时,“为什么不允许X?”的答案是C++提供了另一种实现相同目标的方法 - 正如这里所展示的那样。
3赞 Zach Saw 3/6/2014
这有一个副作用,即当 Foo 需要完全限定以解决歧义时,Foo 的用户需要知道它在命名空间中 ()。所以不,这里展示的不是实现同样的事情。detail::Foo
1赞 Justin Time - Reinstate Monica 7/2/2016
@thomthom 在标头中包含 using 指令的问题在于,任何包含该标头的源文件,或者包含包含该标头的任何其他文件的任何源文件,其中也包含这些 using 指令,很可能源文件的作者甚至不知道它。如果它的范围有限,例如放置在单独的命名空间中,那么这不是问题。
12赞 Omnifarious 12/6/2010 #3

鉴于类作用域的声明不是继承的,这可能有效。该名称仅在该类声明中或嵌套类的声明中有效。但我认为这有点用一个应该更大的想法来超载一个类的概念。using

在 Java 和 Python 中,单个文件以特殊方式处理。可以使用将其他命名空间中的名称注入到文件中的声明。这些名称(好吧,不完全是 Python,但这里解释起来太复杂了)仅在该文件中可见。import

在我看来,这种能力不与类声明挂钩,而是被赋予自己的范围。这将允许在多个类声明中使用注入的名称,如果它有意义,甚至在函数定义中使用。

这是我更喜欢的一个想法,因为它允许这些事情,同时仍然使用声明为您提供类级别的好处:

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赞 Omnifarious 12/6/2010
@Zach Saw - 我的论点是,一个不同的、相关的功能实际上会更好、更清晰。
1赞 Omnifarious 12/6/2010
@Zach Saw - 我是说我们不应该以你所说的形式使用它,因为它的范围太有限了(双关语)。
1赞 Omnifarious 12/6/2010
@Zach Saw - 就我个人而言,我认为存在一个更通用和/或更清晰的特征的想法是反对更有限和更不清晰的特征的一个很好的理由。
3赞 jalf 12/6/2010
通常,标准委员会的工作方式正好相反。有充分的理由将其包含在语言中吗?C++已经是一门庞大的语言。每个新功能都必须证明成本是合理的,以便添加。那么,这样的功能是否足够重要,足以证明使语言变得更大是合理的?
2赞 Kyle Strand 3/12/2015
我有点难以理解这整件事。这是一个功能想法,而不是当前有效的 C++ 构造,对吧?@ZachSaw,如果这不是你要找的答案,你为什么要接受这个答案?using {}
-2赞 Ichimonji10 12/6/2010 #4

命名空间的明显好处是它们可以避免命名冲突。但是,通过在类声明中引入整个命名空间,此优势将失效。System 命名空间中的函数完全有可能与你自己的 Bar::MemberFunc 函数冲突。您甚至在示例代码中添加注释“no conflict with this”时注意到这一点。

您显然不想像这样将整个命名空间引入到您的类中:

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 12/6/2010
匿名命名空间是翻译单元的本地命名空间,而不是文件的本地命名空间。这意味着匿名命名空间中的名称可用于属于该翻译单元的所有其他文件,即使它们是其他头文件。这可以默默地改变随机头文件的含义。
2赞 Zach Saw 12/6/2010
歧义可以像往常一样解决,也可以通过命名空间别名解决。所以你的整个论点是没有意义的。此外,您的未命名命名空间也没有范围限制 - 这是非常糟糕的。
1赞 Declan 3/4/2014 #5

您可以在类声明中使用 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赞 Zach Saw 3/5/2014
在这种情况下,您最终将对类中使用的命名空间下的所有类型进行类型定义。不仅如此,您还必须对该类中使用的其他命名空间下的所有类型进行类型定义!你确实意识到这不切实际,不是吗?在 CPP 文件中,您仍然需要完全限定类型。System::Network::Win32::Sockets
0赞 Declan 3/6/2014
你当然是对的。但这不一定是坏事。通常,人们不希望(或不应该)使用完整的命名空间,而是使用某些标识符(例如Handle)。是的,必须进行额外的键入有点痛苦,就像编写“using System::Network::Win32::Sockets::Handle”一样,但这并不意味着人们应该更喜欢编写“使用System::Network::Win32::Sockets”,因为它的输入较少。它是在细粒度控制/清晰度与更多文本之间进行权衡。
0赞 Declan 3/6/2014
更正:更喜欢写“”使用System::Network::Win32::Sockets“..在 .cpp 文件中,通常可以使用整个命名空间(并避免重复 typedefs),因为它不会包含在其他标头中并弄脏全局命名空间
0赞 Zach Saw 3/6/2014
不要以为一切都与程序员懒惰有关。多打字是我最不关心的。在每个类中定义如此多的类型会使代码变得非常冗长。可读性受到影响。
0赞 Declan 3/10/2014
是的。当您不需要所有命名空间时,使命名空间中的所有内容都“可用”,这很容易出错并污染当前的命名空间。你只需要对你需要的标识符进行类型定义,这通常不是那么多。正如我所说,这是一种妥协。使用更多文本进行更多控制,反之亦然。