避免过度使用命名空间

9

我的库使用了几个嵌套的命名空间,如下所示:

Library name
    Class name 1
    Class name 2
    Class name 3
    [...]
    Utilities
        Class name 1
            [...]
        Class name 2
            [...]
        Class name 3
            [...]
        [...]

"Utilities" 命名空间包含有用的类扩展,这些扩展不值得被包含在实际的类中。
"Library name" 命名空间是必要的,因为它避免了与其他库的广泛冲突,"Utilities" 命名空间是必要的,以避免由于像这样的事情而产生的歧义,而其中的 "Class name" 命名空间避免了针对类似类编写的实用程序之间的名称冲突。
尽管如此,在实践中仍然非常麻烦。例如,考虑以下内容:
MyLibrary::MyContainer<int> Numbers = MyLibrary::Utilities::MyContainer::Insert(OtherContainer, 123, 456);
// Oh God, my eyes...

这让我觉得自己做错了什么事情。有没有更简单的方法来保持组织、直观和明确?

你曾经使用过boost.fusion吗?它们很相似。 - ildjarn
4个回答

17
看看标准库(或Boost)的组织方式。其中几乎所有内容都在单一的std命名空间中。将所有内容放入自己的命名空间中几乎没有什么好处。
Boost将大多数内容放置在boost命名空间内,而重要的库则会获得一个单一的子命名空间(例如boost :: mpl或boost :: filesystem)。库通常定义一个单一的aux子命名空间以用于内部实现细节。
但是你通常不会看到深层或细粒度的命名空间层次结构,因为它们很难使用,并且几乎没有好处。
以下是一些良好的经验法则:
与特定类相关的辅助函数应该与类在同一命名空间中,以使ADL起作用。这样在调用时就不需要限定辅助函数的名称了。(就像如何在定义在std中的迭代器上调用sort而无需调用std :: sort一样)。
对于其他一切,请记住命名空间的目的是避免名称冲突而不是其他任何东西。因此,您的整个库都应位于一个命名空间中,以避免与用户代码发生冲突,但在该命名空间内,除非打算引入冲突名称,否则没有技术上需要进一步的子命名空间。
您可能希望将库的内部分离为子命名空间,以便用户不会从主命名空间中意外地选择它们,这类似于Boost的aux。
但通常情况下,我建议尽可能少地嵌套命名空间。
最后,我倾向于使用简短、易于键入和易于阅读的名称来命名我的命名空间(再次,std是一个好的示范。简短而且到点,几乎从不使用进一步嵌套的命名空间,这样你不必经常写它,并且它不会使源代码太杂乱)。
关于辅助函数和ADL的第一个规则就足以让您将示例重写为以下形式:
MyLibrary::MyContainer<int> Numbers = Insert(OtherContainer, 123, 456);

那么我们可以将 MyLibrary 重命名为,比如说,Lib:

Lib::MyContainer<int> Numbers = Insert(OtherContainer, 123, 456);

如果您能做到这一点,那么你处理的问题就会变得非常可管理。

不同类之间相似实用函数之间不应该有任何冲突。C++允许您重载函数和特化模板,因此您可以在同一个命名空间中拥有Insert(ContainerA)Insert(ContainerB)

当然,只有在实际上有额外的嵌套命名空间时,才可能存在命名空间和类之间的冲突。

请记住,在您的Library命名空间内,只有您可以决定引入哪些名称。因此,您可以通过避免创建任何冲突名称来避免名称冲突。将用户代码与库代码分开的命名空间很重要,因为两者可能互相不知道,因此可能会意外地发生冲突。

但是在您的库内部,您可以给所有东西赋予不冲突的名称。


2
我想给这个点赞五次。我使用过不止一个带有多余的“Utilities”[或类似]嵌套命名空间的库,它们总是让人感觉原始开发人员只是讨厌重载和ADL,并希望确保编译器不能利用它。 - Dennis Zickefoose

7

如果某些东西让你感到困扰,停止它。在C++中绝对没有必要使用深层嵌套的命名空间-它们不是用来作为架构设备的。我的代码总是只使用一个级别的命名空间。

如果你坚持使用嵌套命名空间,你可以为它们创建短别名:

namespace Util = Library::Utility;

那么:

int x = Util::somefunc();   // calls Library::Utility::somefunc()

3

头文件中的声明需要使用命名空间来避免全局命名空间污染:

MyLibrary::Utilities::MyContainer<int> Numbers;

但在源文件中,您可以使用“using”:

using namespace MyLibrary::Utilities;

...

MyContainer<int> Numbers;
Numbers.Insert(OtherContainer, 123, 456);

2
我通常避免使用 using,因为在我的项目中,我经常觉得它会降低对代码来源的理解,但在这种情况下,我强烈建议使用它。如果你不担心歧义,那么这是保持代码整洁的最佳选择。 - pattivacek

2

对我来说,完全限定名称看起来并不那么糟糕,但我喜欢在方法和类名中明确表示。但是using可以帮助解决问题:

您可以在源文件的全局范围内使用using namespace MyLibrary,使其变为:

MyContainer<int> Numbers = Utilities::MyContainer::Insert(OtherContainer, 123, 456);

然后你可以导入你需要的特定函数:

using MyLibrary::Utilities::MyContainer::Insert

然后执行以下代码:

MyContainer<int> Numbers = Insert(OtherContainer, 123, 456);

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