为啥我们不能在一个类中声明一个命名空间?

Posted

技术标签:

【中文标题】为啥我们不能在一个类中声明一个命名空间?【英文标题】:Why can't we declare a namespace within a class?为什么我们不能在一个类中声明一个命名空间? 【发布时间】:2012-11-09 04:19:42 【问题描述】:

在类中声明类是有效的。 (嵌套类)

在类中声明命名空间是无效的。

问题是:有什么好的理由(除了 c++ 语法/语法问题)禁止在类中声明命名空间吗?


至于我为什么要这样做,这里有一个例子:

让我们有一个二叉树容器的基本说明

template<typename Data>
class binary_tree

 public:
  ... stuff ....     

 private:
  ... iterators class declaration ...

 public:
  typedef left_depth_iterator_impl     left_depth_iterator;
  typedef right_depth_iterator_impl    right_depth_iterator;
  typedef left_breadth_iterator_impl   left_breadth_iterator;
  typedef right_breadth_iterator_impl  right_breadth_iterator;

  ... stuff ....     

 private:
  Data         data;
  binary_tree* left;
  binary_tree* right;
;

现在我注意到我的类中有很多迭代器,所以我想像这样在同一个命名空间中重新组合它们:

template<typename Data>
class binary_tree

 public:
  ... stuff ....     

 private:
  ... iterators class declaration ...

 public:
  namespace iterator
  
    typedef left_depth_iterator_impl     left_depth;
    typedef right_depth_iterator_impl    right_depth;
    typedef left_breadth_iterator_impl   left_breadth;
    typedef right_breadth_iterator_impl  right_breadth;
  

  ... stuff ....     

 private:
  Data         data;
  binary_tree* left;
  binary_tree* right;
;

这将允许一个简单的用法:

void  function()

  binary_tree::iterator::left_depth   it;

  ...stuff...

如果我使用一个类而不是命名空间,这会起作用,但是我不得不声明一个永远不会被实例化的类,它是一个相当命名空间。

为什么允许嵌套类并禁止在类中嵌套命名空间?这是遗留的负担吗?


不只引用部分标准(尤其是语法部分)的语义原因的答案将被欣赏:)

【问题讨论】:

请参阅 Eric Lippert 在***.com/questions/8671427/… 上的回答 摘录:默认情况下未实现功能 缺少功能不需要理由。相反,所有功能都必须通过证明其收益大于成本来证明其合理性。作为提出该功能的人,您有责任描述为什么您认为该功能有价值;我没有责任解释为什么不这样做。 罗伯特链接的答案非常好,我希望我能再投票几次。 你可以这样做... #define inclass_namespace struct.. 你可以这样使用它... 在阅读了有关 Smalltalk 及其方法协议的信息后,我遇到了与 @drax 相同的问题,这些协议用于按目的对方法进行分组。参见The Art and Science of Smalltalk,第 66 页,第 2 章。 “标准协议”。协议的目的是帮助编写类和使用它的程序员 我认为@RobertCooper 所说的可能是有效的,但也很有可能实际上提出了一个功能但由于某种原因被拒绝了。或者已经存在更好的选择。 【参考方案1】:

既然您询问了标准授权命名空间位置的哪些部分,我们首先提出了这个问题:

C++11 7.3-p4: 每个命名空间定义都应出现在全局范围或命名空间范围 (3.3.6) 中。

关于类定义和在其中声明命名空间的提议,我带你去......

C++11 9.2-p2: 类在类说明符的结尾 处被视为完全定义的对象类型 (3.9)(或完整类型)。在类成员规范中,类在函数体、默认参数、异常规范和非静态数据成员(包括嵌套类中的此类内容)的大括号或等式初始化器中被认为是完整的。否则,它在其自己的类成员规范中被视为不完整。

因此,一旦达到结束花键,类定义是有限的。它不能打开备份和扩展(派生是不同的,但它不是扩展刚刚定义的类)。

但潜伏在命名空间标准定义的最开始的是扩展它的能力;由于缺乏更好的术语而对其进行扩展:

C++ 7.3-p1:命名空间是一个可选命名的声明区域。命名空间的名称可用于访问在该命名空间中声明的实体;即命名空间的成员。 与其他声明性区域不同,命名空间的定义可以拆分为一个或多个翻译单元的多个部分。(已添加重点)。

因此,类中的命名空间将违反 7.3-p4 中的定义。假设不存在,可能在任何地方声明一个命名空间,包括在一个类中,但是由于一个类的定义在它关闭后就被形式化了,你将只剩下这个能力如果您始终遵守 7.3-p1,请执行以下操作:

class Foo

   namespace bar
   
       ..stuff..
   

   .. more stuff ..

   namespace bar
   
       ..still more stuff..
   
;

在建立 7.3-p4 来解决它之前,这个功能的有用性可能已经争论了大约 3 秒。

【讨论】:

Therefore, a namespace within a class would violate the definition in 7.3-p4. 你是说[...] in 7.3-p1吗? 嗯,这纯粹是技术性的。您可以在可扩展性子句中添加一个例外,说明只能扩展类范围之外的命名空间。轻松愉快。 感谢您的回答,很高兴知道这背后的原因。 “在建立 7.3-p4 解决它之前,这个功能的有用性可能已经争论了大约 3 整秒。”不完全是讨论如何真正起作用。讨论将变成关于 7.3-p4 本身是否有意义的讨论。如果没有 7.3-p4,命名空间将是一个比使用更强大的工具,这是这里提出的整个论点。【参考方案2】:

我将在这里与其他人不同意。我不会说没有真正的优势。有时我只想在没有额外含义的情况下隔离代码。例如,我在一个多线程 ringbuffer 模块中工作,并希望将状态成员(其中一些是原子的和/或内存对齐的)拆分为生产者和消费者的命名空间。

通过使用 producerconsumer 前缀(这是我当前令人讨厌的实现)命名所有内容,我添加了污染,使代码更难阅读。例如。当生产者拥有的所有东西都以producer 开头时,您的大脑在阅读它时会更容易将producerProducerTimer(生产者计时器的生产者副本)自动更正为producerConsumerTimer(消费者计时器的生产者影子)或consumerProducerTimer (生产者计时器的消费者影子)。由于代码不再可浏览,因此调试所需的时间比实际需要的要长。

通过创建嵌套类/结构:

我可能会给下一个维护此代码的开发人员一个想法,即可以/应该在上下文中实例化、复制和分配多个其中的一个,所以现在我不仅担心命名,而且还有给= delete这些东西。 我可以通过结构对齐填充将内存占用添加到上下文中,否则这可能是不必要的。 将所有成员设为静态不是一种选择,因为可以实例化多个上下文,需要其自己的生产者/消费者状态变量。 此类结构的函数不再有权访问其他成员数据或函数,例如双方共享的常量或函数,而是必须将这些内容作为参数。

理想情况下,我希望能够像这样进行更改:

rbptr producerPosition;
rbptr consumerPosition;

到这里:

namespace producer

    rbptr position;

namespace consumer

    rbptr position;

然后,应该只接触消费者成员的函数可以使用消费者命名空间,只应该接触生产者成员的函数可以使用生产者命名空间,而需要接触两者的函数必须明确限定它们。在仅使用生产者命名空间的函数中,不可能意外触及消费者变量。

在这种情况下,希望纯粹是为了减少事物的生产者和消费者副本之间的命名冲突,而减少命名冲突正是命名空间存在的目的。出于这个原因,我支持能够在类中声明命名空间的提议。

【讨论】:

我确实同意这个观点,标记的答案被标记为已接受,因为它确实回答了“为什么目前不是这种情况”的问题,主要答案是“你可以用另一个做得不好功能所以还没有人提出这个”,这并不意味着它必须保持这种状态,我也会支持这样的提议,有人提出的那一天:) 添加 get&set 操作符(cast + assign),你就拥有了 properties(免费 - 类/结构中命名空间的另一个特性)。 我支持这个功能。【参考方案3】:

在语言中添加这样的功能并没有真正的优势。除非有需求,否则通常不会添加功能。

类中的命名空间会给你带来什么?你真的宁愿说binary_tree::iterator::left_depth 而不是简单的binary_tree::left_depth 吗?也许如果你有多个命名空间,你可以用它们来区分binary_tree::depth_iterator::leftbinary_tree::breadth_iterator::right

无论如何,你可以使用内部类作为穷程序员的命名空间来达到预期的效果,这也是为什么类内部不需要真正的命名空间的更多原因。

【讨论】:

“除非有需求,否则通常不会添加功能。”我们正在谈论 C++,对吧? 这对于模板类使用命名空间内的某物作为某物的“其”版本的位置很有用。例如T::命名空间::枚举值 命名空间(几乎)完全是语法糖,在类内外。那么为什么有 thaem 呢? 当您不想混淆具有相似名称的变量时(因为它们做类似的事情,但在代码中的不同位置),这将很有用。当然,实现这一点总是有不同的方法,但是当你不想为此使用额外的结构时,类中的命名空间将是一个简单的解决方案。 @einpoklum:“命名空间(几乎)完全是语法糖,在课堂内外。那么为什么要拥有它们呢?” 当然,让我们回到公寓、脆弱、笨拙、不完整、不灵活、过于冗长、重复(等等)VOLUNTARY_NAMING_CONVENTIONSStatic::items::for::scoping)而不是适当的、编译器支持的、“语义的”、灵活的(等等)“真实的”命名空间。 【参考方案4】:

这不是命名空间的重点。命名空间旨在更接近代码的顶层存在,以便两个不同的公司(或代码库)可以相互混合代码。在更微观的层面上,我使用用于电子邮件访问的 IMAP 和用于发送电子邮件的 SMTP 进行编码,并且(可以,我正在大大简化)在任何一个名为 Email 的模块中都有完全不同的类,但我可以有一个应用程序,比如说一个邮件客户端,想要同时使用同一个类中的两者,例如也许它将邮件从一个帐户转发到另一个帐户。命名空间/包名称/等允许这样做。

您提出的根本不是命名空间的用途-在一个文件中,您可以为不同的事物命名,因为作者对该文件具有全球知识,尽管当两家公司想要共享代码时,情况并非如此或者两个不知道它们会在任何时候发生冲突的应用程序。

【讨论】:

【参考方案5】:

只是一个我觉得值得一提的小想法。在类中使用命名空间的一种方式是在功能上等同于模板命名空间。

template<class...types>
struct Namespace 
    namespace Implementation 
        ...
    
;

// somewhere else

using namespace Namespace<types...>::Implementation;

// use templated stuff.

就个人而言,我会喜欢这个功能,但似乎需求还不够高,无法实现。

【讨论】:

这对于在新范围内导入嵌套枚举类型的值也很有用。 @Svalorzen 是的。类和命名空间非常相似,所以我不确定为什么这么多人反对这个想法。 @AnthonyMonterrosa,其中一部分只是通常根深蒂固的“坚持你拥有的”态度,与经典bikeshedding "antirant" of Poul-Henning Kamp中如此雄辩地写成的态度重叠。还有一部分原因是,在几十年前、庞大且已经非常复杂的 C++ 规范中改变任何东西既困难又冒险。【参考方案6】:

我可能会因此而讨厌

template<typename Data>
class binary_tree

 public:
  ... stuff ....     

 private:
  ... iterators class declaration ...

 public:
  class iterator
  
    iterator() = delete;
  public:
    typedef left_depth_iterator_impl     left_depth;
    typedef right_depth_iterator_impl    right_depth;
    typedef left_breadth_iterator_impl   left_breadth;
    typedef right_breadth_iterator_impl  right_breadth;
  

  ... stuff ....     

 private:
  Data         data;
  binary_tree* left;
  binary_tree* right;
;

这基本上是一个无法实例化的静态类,因此您必须使用 iterator::type 访问其中的 typedef 因此您现在可以将其用作常规命名空间

void  function()

  binary_tree::iterator::left_depth   it;

  ...stuff...

【讨论】:

嗨,在接受的答案中已经提到了嵌套类的选项。在任何情况下,请注意,仅发布代码而不解释代码如何回答问题并不是一个好习惯。请编辑您的答案以解释您的建议是什么以及它的好处。另请注意,OP 在问题中明确指出他知道嵌套类。您好,询问原因不是为了解决问题。 是的,我会添加详细信息 但答案给出了不同的方法。我做了他们想要的。类内的命名空间 好的,所以 OP 知道但没有提供任何代码。我回答是因为我知道有人会问同样的问题,并且可能不会阅读整个问题。有时你必须替别人回答。 你是正确的,接受的答案建议使用嵌套类(就像你的答案一样)。但正如你所看到的,这不是得票最多的答案。你想为别人展示一个好的解决方案是可以的。无论如何。正如我所写 - 您需要详细说明并解释您的解决方案

以上是关于为啥我们不能在一个类中声明一个命名空间?的主要内容,如果未能解决你的问题,请参考以下文章

为啥命名空间中定义的元素不能显式声明?

为啥 C++ 友元类只需要在其他命名空间中进行前向声明?

为啥要将类型放在未命名的命名空间中?

为啥内联未命名的命名空间?

PHP的 Final关键字类(文件)的加载和命名空间

函数重载细说