类与命名空间之争,还是类和命名空间共存?

12

既有类又有命名空间?

这个问题是关于一个我越来越多地使用的模式:为相关概念同时创建类和命名空间。我认为这主要是由于C++语言工件的影响,但不完全如此。

我想最重要的问题是:这样做好吗?为相关概念同时创建类和命名空间?

更低级别的问题:

如何做到最好?在命名空间内嵌套类?

namespace Foo_Namespace {
   class Foo_Class {
       ...
   };
}

或者分开、对等、类和命名空间?

class Foo_Class {
    ...
};
namespace Foo_Namespace {
   // non-class free functions, etc.
}

我必须承认,我倾向于在命名空间内嵌套类。即使这导致名称很丑陋。

但是,即使我这样做,我应该使用什么命名约定呢:

以下方式太长了,会导致非常丑陋的名称Foo_Namespace :: Foo_Class

namespace Foo_Namespace {
   class Foo_Class {
       ...
   };
}

不需要在名称中使用任何后缀或指示符:

namespace Foo {
   class Foo {
       ...
   };
}

但是当我查看Foo::bar()时,我感到不确定,这是在命名空间::Foo中的自由函数bar,即::Foo::bar(),还是在命名空间::Foo::Foo::bar中的类Foo的成员函数。

而像::Foo::Foo::bar这样的名称仍然不太好。

目前我正在做的是:

在名称中不需要使用任何后缀或指示符:

namespace Foo_ns {
   class Foo {
       ...
   };
}

主要是因为我通常先创建类,然后意识到一个命名空间会很好。

我想知道是否应该恢复我多年未使用的命名约定:使用 _c 表示类,_ns 表示命名空间:

namespace Foo_ns {
   class Foo_c {
       ...
   };
}

详情:

我不会重复我之前说过的,但我会补充一些细节。

我知道使用命名空间而不是类的最实用原因是,您可以在命名空间中进行自由函数的前向声明,但是您不能对类的某些方法进行前向声明。也就是说,使用类时,您必须全部或不声明。而对于自由函数,特别是命名空间中的自由函数,您可以逐个声明。部分内容可以在不同的头文件中声明。您可以仅使用一个或两个函数的前向声明,而不是#包含庞大的头文件库以包含大量的类。等等。

(例如,请参见http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Forward_Declarations,尽管Google更倾向于包括头文件而非前向声明。)

另一个原因是命名空间允许保持类本身较小。

对于类而言,最大的优势是类可以传递到模板,而命名空间无法传递到模板。

我喜欢将类嵌套在命名空间中,如Foo_ns / Foo_ns :: Foo_c,而不是将它们作为同级对象Foo_ns / Foo_c,因为经常需要帮助类,例如Foo_ns / Foo_ns :: Foo_c / Foo_ns :: Foo_Helper_c。如果类和命名空间是同级对象,则将Foo_ns / Foo_c但是Foo_ns :: Foo_Helper_c似乎很奇怪。

我喜欢使用命名空间,因为我同意Andrei Alexandresciu的观点:http://laser.inf.ethz.ch/2012/slides/Alexandrescu/1-C++%20course%20parts%201%20and%202.pdf

 Class methods vs. free functions

 • Conventional wisdom: methods are cool, free functions are so 1960s
 • Yet:
   ◦ Free functions improve encapsulation over methods
   ◦ Free functions may be more general
   ◦ Free functions decouple better
   ◦ Free functions support conversions on their left-hand argument

  Surprising fact #2

    Making a function a method should be
    your last, not first, choice

  (c) 2012– Andrei Alexandrescu. 32 / 59

创建自由函数比在类中使用方法更好,但有时必须使用类,例如用于模板。

命名约定方面,我过去使用了Foo_ns / Foo_ns :: Foo_c,现在使用的是Foo_ns / Foo_ns :: Foo。

(顺便说一句,我倾向于使用带下划线和大写字母开头的类名,而不是驼峰命名法。)

如果有意义,我可能会省略命名空间上的 _ns 后缀,例如当命名空间和类不需要具有相同的名称时。

我不喜欢让它们具有相同的名称。考虑一个命名空间 foo 中的类 Foo 的构造函数:

:: Foo :: Foo :: Foo ()   VS   :: Foo_ns :: Foo :: Foo ()

后者并没有好到哪里去,但要清晰一些。

我认为我通常会独立创建类,而不将其嵌套在命名空间中。事实上,我可能会添加一些静态方法,然后才意识到嵌套在命名空间中的类会更好。此时,重构可能很麻烦,我有时会创建转发函数,从类静态方法转发到命名空间中的自由函数,反之亦然。这使我后悔没有从第一步直接跳转到命名空间内的类。

结论

我目前的最佳做法是Foo_ns / Foo_ns :: Foo,即

namespace Foo_ns {
   class Foo { 
   ...
   };
}

我会感激任何建议,任何改进。

或者说,我这样做是不是就是有问题?


这实际上完全取决于个人。我 个人 喜欢将相关的函数和类分组放置在一个命名空间中。比如说,如果我正在制作一个网络程序,则所有与实际网络有关的功能都会放在“net”命名空间中,而配置(例如编译时和运行时配置以及参数解析)则会放在“config”命名空间中。但这只是我的想法。 - Some programmer dude
关于您是否困惑它是成员函数还是命名空间中的自由函数:这就是命名指南派上用场的地方。我从未见过一个没有明显区分两者的命名指南,我认为任何这样做的指南都应该仅用于向后兼容的原因。Google使用CamelCase表示类,使用全小写表示命名空间。 - Voo
@Voo:我认为命名空间是一个没有对象实例的开放类。 - Krazy Glew
@KrazyGlew,既然您已经承认了命名空间和类相同的混淆问题,我认为您应该看到在这方面更改命名准则的价值 ;) foo::bar()Foo::bar() 的区别非常明显。 - Voo
3个回答

12

我建议将您的类放在一个与相关函数有关的命名空间中。这样做的一个重要原因是参数依赖名称查找(ADL)。当您调用一个具有类类型参数的非成员函数时,函数名将在该类的封闭命名空间中查找。因此,如果您有:

namespace foo {
  class bar { };
  void baz(bar);
}
如果您想要调用 baz,您无需明确给出它所在的命名空间,只需执行以下操作:
foo::bar x;
baz(x);

即使没有为baz加限定符,编译器仍然找到了该函数,因为它位于一个包含其参数类型的命名空间中。这种方式下,C++ 认为类所在的封闭命名空间的内容是该类接口的一部分。以这种方式实现作为类接口一部分的函数,作为非成员非友元函数,允许 ADL 查找该函数,从而提高了封装性。如果一个函数不需要访问类的内部,就不要把它作为类的成员 - 取而代之,将它放在与该类相同的命名空间中。

然而,你的命名空间和类名称相同是有问题的。通常,在一个命名空间中会有多个类,即使没有,它们的名称也应该不同。命名空间的名称应描述其内容,而不仅仅是其中的单个类。


2
+1 表示 C++ 认为类所在命名空间的内容是该类接口的一部分 - Jesse Good
ADL - 很好的观点。// 至于命名空间中有多个类 - 我想我要说的是,这通常是我转换到命名空间的地方,但现在我正在考虑更早地转换,即使命名空间中只有一个类。 - Krazy Glew

4
Foo_ClassFoo_Namespace分开是绝对错误的,这将阻止您使用参数依赖查找在命名空间中查找与类一起使用的函数。因此,如果命名空间中有使用类类型的参数的函数,则应将类嵌套在命名空间中。
为类和命名空间使用相同的名称有点令人困惑,在某些情况下可能导致歧义。以大写字母开头的命名空间名称也很不寻常。如果您的类名称以大写字母开头,则可以选择命名空间foo和类Foo,从而得到foo :: Foo
命名空间是否不会包含多个类?这听起来对我来说很不寻常。我会根据所有内容而不是一个类型来为命名空间命名。例如,如果是socket类,则将其放入networking命名空间中。
我认为在类上使用_c后缀完全荒谬。我也不喜欢_ns后缀在命名空间上,它唯一的优点就是Foo_ns :: Foo Foo :: Foo 更好,但我仍然会将其改为foo :: Foo
(您的其余问题似乎只是“为什么命名空间很好”,这不需要解释,它们被添加到语言中是有原因的,并且建议将接口的部分设置为非成员。总体上使用头文件而不是前置声明是正确的,但这并不改变您可以在单独的头文件中重新打开命名空间以向命名空间添加新名称的事实,这是您所描述的优势。)

2

我不喜欢使用"分形"名称重复的东西,尽量避免这种情况。对于你的情况,我会尝试使用类似命名空间 "Foo" 和嵌套类 "Type" 的方式进行命名。也许其他名称更合适,但这要根据你实际的用例而定。下面是一个我发现自己一遍又一遍地重复使用的用例:

namespace status {
    enum type {
        invalid,
        okay,
        on_fire
    };
    // debug output helper
    char const* to_string(type t);
}

枚举类型被称为status :: type,可以取值如status :: okay。在我看来非常易读。

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