在C++中,处理命名空间有正确的方法吗?

8
我被Java、C#和AS3感染了,我一直想以同样的方式使用C++命名空间,但我不断地阅读到它们并没有真正为此而设计。
有正确的使用命名空间的方法吗?例如,在由十几个库项目(比如图形、声音、数学等)和几个应用程序项目组成的代码库中,该怎么做?是否正确/错误/开发人员自定义:
- 将所有内容放在顶级 MyCompanyNameMyProjectName 命名空间下? - 为每个库创建一个子命名空间?
C++中是否有官方规则/指南,还是只有人们倾向于遵循的建议?

5
这是一个非常明确的问题,有一个确定的答案。它没有任何主观性或争论性。 - Björn Pollex
什么?我认为这非常主观。当然,我们可以追求严谨,说他只是在问一个简单的“是/否”问题:没有官方指南,有一些人们倾向于遵循的非官方建议。但他真正想知道的是那些准则是什么,正确处理命名空间的方法是什么。而这是主观的。但是,当然,这并不意味着应该关闭它。问题是可以回答的,并且答案可能有价值,也可能好坏不一,即使它们是主观的。 - jalf
1
所以,是的,这是主观的,但不,它并不具有争议性(就近投票而言,这才是最重要的部分),也不应该被关闭。 :) - jalf
很少有问题只有是/否的答案。这并不意味着没有基于官方语言规范或事实上的标准的“正确答案”。 - Mr. Boy
3个回答

15

正如我在对@Nim的回答中提到的评论中所说,关键是要设计出一个实用的结构。

如果你看一下.NET框架,它的命名空间有着很长的名称并且嵌套非常深(例如,System.Collections.Generic.List<T>)。Java也是如此。你会发现这些命名空间的名称非常冗长而且嵌套层次很深,但实际上并没有什么作用。

当我第一次创建一个List类时,我就会遇到名称冲突的问题,而这正是命名空间应该避免的。 (由于这似乎给某位评论者留下了错误的印象,当然我能够创建自己的List类。但每次我使用它时,都有可能遇到冲突。 "标准"的List或其命名空间都非常常用,因此我很可能会在那个命名空间上使用using,这意味着,如果我还想在同一个文件中引用自己的List类,我就会遇到冲突)

相比之下,C++标准库将几乎所有内容都放在一个简单的std命名空间中(还有一些内容放在std::ios中)。

实际上,.NET命名空间是一种浪费精力的做法。它们没有任何作用。每个代码文件都以长长的using X; using Y; using Z列表开始,因此当你编写代码时,这些命名空间实际上都没有生效。为了创建一个int类型的列表,我只需要使用List<int>。命名空间基本上已经消失了。

在C++中,人们不鼓励使用using namespace std,因此为了使用标准库符号,我们使用std前缀:std::vector<int>

Boost使用了类似的策略:几乎所有内容都在一个名为boost的根命名空间中。一些复杂的库则放置在子命名空间中,例如boost::filesystemboost::mpl,而一些库则有“仅内部使用”的命名空间,通常被命名为aux,但它们旨在对用户不可见(至少被忽略)。
这更好地保留了命名空间的优势:避免命名冲突。我可以(并且人们经常这样做,因为vector可能有几个略微不同的含义)定义自己的vector类,它不会与std::vector冲突。
那么你的命名空间应该多深嵌套呢?我倾向于采用标准库加上一点的方式。不要为每个东西发明新的命名空间,保持命名空间名称简短。
也许你公司的所有代码都使用一个共同的根命名空间,比如Cpy
在此之下,你可能会为每个产品创建一个命名空间,例如Prod。再往下呢?你可能会为每个主要组件(Comp)创建一个命名空间,基本上就是这样了。
重要的是,你几乎不需要引用最外层的命名空间。你编写的代码将位于Cpy::Prod中,因此你需要输入的唯一命名空间前缀是Comp::,它像标准库的std一样简短而简洁。我几乎从不必写Cpy::Prod::Comp,因为我的所有代码已经在前两个命名空间中了。如果情况不是这样,命名空间结构将会太长并且嵌套过深。

有时候,我像 Boost 一样创建小的“内部”帮助命名空间,通常称为 AuxUtil 等。这些应该很小,并且只能从它们的直接父命名空间访问。一个特定的组件可能会有一些仅供内部使用的类。因此,它们被放在一个小的 Aux 命名空间中,主要是为了将它们隐藏起来,防止其他引用我们组件的人看到。

当然,您可能会问自己最外层的“公司”命名空间真正的目的是什么。难道不是我们所有的代码都属于公司吗?第三方代码通常(希望)会在其自己的命名空间内(例如,boost:: ),因此即使没有公司命名空间,我们也不应该冒任何冲突的风险。您可能想删除它。

还有一件事需要考虑,如果您正在编写为外部使用的库组件,则命名空间结构应该更加简短。在公司内部,您可以假设每个人都已经在“公司”命名空间内编写代码,因此他们永远不必输入命名空间前缀。这基本上是“免费”的。但是,如果外部用户必须引用您的代码,则他们必须输入另一层命名空间,这很快就会变得烦人。一个总体的“产品”命名空间可能是不可避免的(类似于 stdboost),但是您可能希望尽可能避免使用“产品”命名空间。(同样,像这两个示例,只在根命名空间下很少创建子命名空间)

关键是你的命名空间必须是实用可用的。如果你强制用户在命名空间前缀中输入超过8个字符,这会很烦人,他们只会到处添加using namespace X。你真正需要使用命名空间的情况是防止名称冲突。你控制的代码永远不应该与第三方代码共享命名空间,因为你无法控制他们添加的名称(通常,第三方库通过将其库放入公共根命名空间(如boost)来解决此问题)。但是,在你控制的代码中,也不会有那么多碰撞。所以不要定义比你实际需要的更多的命名空间。


在C#中,如果您使用using System.Collections.Generic并定义自己的List<T>类,则不会出现任何名称冲突。甚至,如果您在同一范围内有一个名为List的命名空间和一个类List,也不会出现名称冲突。using并没有“消除”命名空间。在头文件中,C++程序员只是被劝阻使用using namespace std。在源文件中使用它很少有缺点;只有在您实际需要消除歧义时才应使用完整的命名空间名称(或别名)。 - Steve M
3
@Steve:如果我在我的代码文件中提到“标准列表”,通常会在文件顶部加上 using System.Collections.Generic,是吗?如果在同一文件中,我需要引用我自己在 Jalf.SomeOtherNamespace 中定义的 List,那么我还会在文件顶部加上 using Jalf.SomeOtherNamespace。现在,在文件主体中,如果我键入 List<int>,就会出现名称冲突。标识符 List<T> 是有歧义的。所以,是的,在C#中我会遇到名称冲突,因为命名空间在它们实际有用的任何时间都被选择性地禁用了。 - jalf
很有说服力的回答。然而,我不同意C#/Java设置是“无用的”。它不是一个直接可比较的语言特性。 - Mr. Boy
2
@Steve:如果没有冲突,怎么可能会有歧义呢?;) 无论如何,正如你所说,开发人员最现实的选择之一是输入完整限定名称,在这些平台上,这个名称非常长。而且.NET允许您为单个类定义别名,但不能为整个命名空间定义别名,这可能会使它成为一项繁琐的任务。如果他们的命名空间结构不是那么复杂,处理起来就更容易了,这才是我的观点。在C++中,这些解决方法的成本要低得多,我只需要添加一个std::前缀,而不是在.NET中常见的长长的前缀。 - jalf
@Steve:你对此无能为力。只要它存在,你就会有错误。唯一能做的就是以某种方式重写代码,使问题不再出现。(否则,任何情况都不会构成碰撞,不是吗?总有办法解决)。不管怎样,有趣的是 using 也可以为命名空间设置别名。出于某种原因,我从来没有成功过,但我也从未查过,所以我认为这是不可能的。谢谢。 :) - jalf
显示剩余4条评论

3
我认为这是非常主观的,你可能会收到很多不同的意见,所以这是我的观点! :)
嵌套命名空间-好。一个大型命名空间-可怕。
以上是考虑到实际情况(即非常大的代码库)的。为了防止RSI,请考虑使用短的命名空间名称! ;)
编辑:一个例子,这是高度主观的,其他人会有不同的方法...
假设你在Java中为公司foo工作,通常你从com开始,因此改用foo。
namespace foo
{

}

现在你公司所有的代码都在一个命名空间中,接下来考虑任何共享的内容:
namespace foo
{
  namespace com
  {
    // any communication stuff goes here
    namespace msg
    {
      // let's say you have some form of messages that you send
    } // msg
    namespace con
    {
      // wraps all your connectivity
    } // con
  } // com

  namespace util
  {
    // utilities
    // you may not need to break this up further
  } // util
} // foo

现在假设你有一个名为Bob的项目,在项目中给它分配一个独立的命名空间。
namespace foo
{
  namespace proj
  {
    // all projects are here
    namespace bob
    {
      // all bob's stuff is here, break it up as you need (though I wouldn't bother unless you have hundred's of classes where collisions are likely), I'd rather organise the files in the file system appropriately and keep the classes in the same namespace.
    } // bob

  } // proj
} // foo

3
当你使用命名空间时,可以轻松创建短别名,因此给命名空间取长名称是完全可以的(如果这有助于代码的清晰度)。 - Björn Pollex
你可以加上一个示例结构吗?顺便说一句,我从来不知道NS别名。大加赞赏。 - Mr. Boy
1
@jalf - 因此鼓励使用别名...(例如对于John namespace p_bob = foo::proj::bob; 然后 p_bob::myclass... - Nim
@jalf - 一方面是为了清晰度,另一方面是避免命名冲突,不过你可以通过命名来解决这个问题 - 这就归结为个人喜好了。我更喜欢使用bob::Time和kim::Time而不是bobTime和kimTime,并且每次在项目中需要一个Time类时都要记得这样做(这只是一个愚蠢的例子,但希望你能理解我的意思!)- 但正如我上面所说的,你需要运用一点常识... - Nim
但是在C++中,语法很糟糕,因为使用多个命名空间时嵌套和缩进会变得很深。 - Beachwalker
显示剩余5条评论

0
对我来说,简而言之,在索引文件中使用命名空间并没有问题,但是在.h或头文件中几乎不会使用它们,因为很多时候东西会发生改变或添加,所以拥有一个大的命名空间可能是一个大错误。 然而,对于较小的独立文件来说,拥有较小的独立命名空间并不会造成太大的影响,我认为。

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