为什么和如何在C++中使用命名空间?

35

除了使用STL函数之外,我以前从未为我的代码使用过命名空间。

  1. 除了避免名称冲突之外,还有什么其他原因可以使用命名空间吗?
  2. 我必须在命名空间作用域内同时包含声明和定义吗?
6个回答

28
经常被忽视的一个原因是,仅通过更改一行代码以选择一个命名空间而不是另一个,您可以选择备选的功能/变量/类型/常量集-例如协议的另一个版本或单线程与多线程支持,操作系统支持平台X或Y-进行编译和运行。通过包含具有不同声明的标头或使用#define和#ifdefs也可以实现相同的效果,但这会粗略地影响整个翻译单元,并且如果链接不同的版本,则可能会出现未定义的行为。使用命名空间,您可以通过使用命名空间进行选择,该命名空间仅适用于活动命名空间中,或通过命名空间别名进行选择,因此它们仅在使用该别名的位置应用,但它们实际上解析为不同的链接器符号,因此可以组合而不会出现未定义的行为。这可类似于模板策略的使用方式,但其效果更加隐式,自动和普遍-这是一种非常强大的语言特性。
更新:回应marcv81的评论...
“为什么不使用具有两个实现的接口?”
“接口+实现”在概念上就是选择要别名的命名空间所做的事情,但如果您特指运行时多态性和虚拟分派:
- 生成的库或可执行文件不需要包含所有实现,并且在运行时不断直接调用所选的实现。 - 由于一个实现被合并,编译器可以使用无数优化,包括内联、死代码消除,并且不同“实现”的常量可以用于例如数组大小-允许自动内存分配而不是更慢的动态分配。 - 不同的命名空间必须支持相同的使用语义,但不必支持虚拟分派的确切函数签名集。 - 使用命名空间,您可以提供自定义的非成员函数和模板:这在虚拟分派中是不可能的(非成员函数有助于对称操作符重载-例如同时支持22 + my_type和my_type + 22)。 - 不同的命名空间可以指定不同的类型用于某些目的(例如,哈希函数可以在一个命名空间中返回32位值,在另一个命名空间中返回64位值),但虚拟接口需要具有统一的静态类型,这意味着笨拙和高开销的间接引用,如boost::any或boost::variant,或最坏情况下的选择,其中高阶位有时是无意义的。 - 虚拟分派通常涉及在大接口和笨拙的错误处理之间进行妥协:使用命名空间,可以选择在不合适的命名空间中简单地不提供功能,从而给出了必要的客户端移植工作的编译时强制执行。

2
我在C++方面的经验远不及你,但这听起来对我来说相当恶劣。为什么不使用具有两个实现的接口?我看不出使用命名空间的任何优点。请原谅我可能的幼稚评论 :) - marcv81
@marcv81:上面有一些解释...欢迎提出问题/想法。干杯。 - Tony Delroy
1
惊人,+1。我理解速度/大小优化。对于改进的灵活性,您是否在说我们可以例如使用C++11“auto foo = bar()”,其中bar()具有不同的返回类型,具体取决于所使用的命名空间?尽管我理解它,但我不确定我会在工作中尝试它 :) 我能看到的一个缺点是单元测试:我宁愿不链接超过1个可执行文件来运行我的测试,但我同意这并不是每个人都会遇到的问题。顺便说一下,这也是我喜欢避免#ifdef的原因。 - marcv81
1
@marcv81:是的,可以使用auto foo = bar();。这可能会使测试变得复杂,但您可以执行诸如void test1() { using ns1; TEST_EQ(x, y()); }之类的操作 - 对于test2/ns2也是如此 - 并在一个可执行文件中运行两者。有时使用宏来生成具有不同using nsN;的类似测试函数将简化事情,但编写长宏会变得丑陋。将测试(特别是函数体)移动到多次包含的支持文件中是另一种选择。(对于#ifdef的代码,其中相同的符号或函数采用不同的定义,这种测试通常是不可能的)。 - Tony Delroy

18

这里有一个好理由(除了您提到的明显原因外)。

由于命名空间可以是不连续的,并且可以跨越翻译单元分布,它们也可以用于将接口与实现细节分开。

命名空间中名称的定义可以在同一命名空间中或任何封闭命名空间中提供(使用完全限定名称)。


您提供的链接对我不可用。我无法访问它。 - black

10

它可以帮助你更好地理解。

例如:

std::func <- all function/class from C++ standard library
lib1::func <- all function/class from specific library
module1::func <-- all function/class for a module of your system

你也可以将其视为系统中的模块。

对于编写文档也很有用(例如:您可以轻松地在doxygen中记录命名空间实体)


3
  1. 难道名字冲突不足以作为一个理由吗?另外,ADL细节尤其是在操作符重载方面也需要考虑。

  2. 这是最简单的方法。您还可以在定义时使用命名空间前缀,例如 my_namespace::name。


2
您可以将命名空间视为应用程序的逻辑分离单元,这里的逻辑意味着假设我们有两个不同的类,将这两个类分别放在一个文件中,但当您注意到这些类共享某些内容足以归类为一个类别时,这是使用命名空间的一个强有力的理由。

1
  1. 回答:如果你想要重载new、placement new或delete函数,那么你会想要在一个namespace中进行。没有人希望被强制使用你的new版本,如果他们不需要你所需的内容。
  2. 是的

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