避免过度使用名称空间

Avoiding Over-Use of Namespaces

本文关键字:空间      更新时间:2023-10-16

我的库使用了几个嵌套的名称空间,布局如下:

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)是如何组织的。几乎所有这些都在单个std名称空间中。把所有东西都放在自己的名称空间中,几乎没有什么好处。

Boost将大多数内容放在boost中,而主要库获得单个子命名空间(例如boost::mplboost::filesystem)。库通常为内部实现细节定义单个aux子命名空间。

但是您通常不会看到深度或细粒度的命名空间层次结构,因为它们使用起来很痛苦,而且从中几乎没有任何好处。

这里有一些好的经验法则:

与特定类相关的辅助函数应该与类在相同的命名空间中,以使ADL能够工作。然后,在调用helper函数时根本不需要限定它的名称。(就像在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名称空间中,单独决定引入哪些名称。所以你可以避免名字冲突,只要不创建任何冲突的名字。使用名称空间将用户代码与库代码分开是很重要的,因为两者可能不知道彼此的存在,因此可能会无意中发生冲突。

但是在你的库中,你可以给所有东西起一个不冲突的名字

如果疼痛,就不要做。在c++中绝对没有必要使用深度嵌套的命名空间——它们不是架构设备。我自己的代码总是使用单级命名空间。

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

namespace Util = Library::Utility;

:

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

头文件中的声明要求命名空间不能污染全局命名空间:

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

但是在源文件中你可以使用using:

using namespace MyLibrary::Utilities;
...
MyContainer<int> Numbers;
Numbers.Insert(OtherContainer, 123, 456);

对我来说,完全限定名实际上看起来并没有那么糟糕,但我喜欢在方法和类名中显式。但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);