【问题标题】:Should every class have its own namespace?每个类都应该有自己的命名空间吗?
【发布时间】:2010-04-01 22:44:08
【问题描述】:

困扰我一段时间的事情:

目前的观点是类型应该保存在一个命名空间中, 包含属于该类型的非成员接口的函数(请参阅 C++ 编码标准 Sutter 和 Alexandrescu 或 here),以防止 ADL 引入不相关的定义。

这是否意味着所有类都必须有自己的命名空间?如果 我们假设一个类在未来可能会通过添加 非成员函数,那么将两种类型放在 与它们中的任何一个相同的命名空间都可能引入非成员函数 这可能会干扰对方。

我问的原因是命名空间对我来说变得很麻烦。我是 编写一个仅限标头的库,我发现自己使用类名称,例如 项目::组件::类名::类名。他们的实现调用 辅助函数,但因为它们不能在同一个命名空间中,所以它们也有 完全合格!

编辑:

一些答案​​表明,C++ 命名空间只是一种避免名称冲突的机制。事实并非如此。在 C++ 中,接受参数的函数使用 Argument Dependent Lookup 解析。这意味着当编译器试图找到与函数名称匹配的函数定义时,它会在寻找候选对象时查看 与其参数类型相同命名空间中的每个函数强>.

这可能会产生意想不到的、令人不快的后果,详见A Modest Proposal: Fixing ADL。 Sutter 和 Alexandrescu 的规则状态永远不会将函数放在与类相同的命名空间中,除非它是该类接口的一部分。除非我准备好为每个类提供自己的命名空间,否则我不明白如何才能遵守这条规则。

欢迎提出更多建议!

【问题讨论】:

  • “它们的实现调用必须完全限定的辅助函数” - 如果有帮助的话,您可以在函数体中放置 using 声明或指令。
  • 出于对所有神圣事物的热爱,不要在类和命名空间之间强制执行 1:1 的比例。破坏了拥有命名空间的全部目的,并导致大量额外的工作。
  • @James D:我不同意。我认为这还远远不够。我们还需要确保每个命名空间本身都在一个命名空间中。
  • 那么告诉我,将void foo(int) 放在与class bar; 相同的命名空间中究竟有什么危害? ADL究竟是如何给我们带来任何问题的?
  • @jalf 如果我对 Sutter 的理解正确,当将 bar 传递给函数 void baz(bar b) 时,baz 的实现会从 bar 中引入 all 函数的命名空间。如果baz 碰巧调用了一个名为foo 的东西,而void foo(int) 碰巧比它之前的匹配更好,那么它选择了错误的foo

标签: c++ namespaces coding-style


【解决方案1】:

没有。我从来没有听过那个约定。通常每个库都有自己的命名空间,如果该库有多个不同的模块(例如功能不同的不同逻辑单元),那么这些 可能 有自己的命名空间,尽管每个库一个命名空间就足够了。在库或模块命名空间中,您可以使用命名空间detail 或匿名命名空间来存储实现细节。每个类使用一个命名空间,恕我直言,完全是矫枉过正。我肯定会回避那个。同时,我强烈敦促您为您的库至少拥有一个命名空间,并将所有内容放在该命名空间或其子命名空间中,以避免与其他库的名称冲突。

为了更具体,请允许我以可敬的Boost C++ Libraries 为例。 boost 中的所有元素都位于boost:: 中。 Boost 中有一些模块,例如进程间库,它们有自己的命名空间,例如boost::interprocess::,但大多数情况下,boost 的元素(尤其是那些非常频繁和跨模块使用的元素)只是驻留在boost:: 中。如果您查看 boost,它经常使用 boost::detailboost::<i>name_of_module</i>::detail 来存储给定命名空间的实现细节。我建议你以这种方式为你的命名空间建模。

【讨论】:

  • +1 表示“否”,但是我不会每个库都有一个 - 库是一个实现单元。命名空间通常与团队/项目/子项目有关。你可以有一个跨越 2 个库和一个 exe 的命名空间
  • @pm100,是的,如果您有一个多模块项目(一个具有多个库或其他逻辑单元),那么您可以将它们统一在一个命名空间下,但是我仍然建议在内部细分每个库有一个命名空间。
【解决方案2】:

不,不,一千次不! C++ 中的命名空间不是架构或设计元素。它们只是一种防止名称冲突的机制。如果在实践中没有名称冲突,则不需要命名空间。

【讨论】:

  • “它们只是一种防止名称冲突的机制。”错误的。命名空间也用于指导 ADL。无论如何,如果他们所做的事情在某些特定情况下是不可取的,那么他们“为”什么远不如他们实际做什么重要。
  • 命名空间正是不是一种简单的命名机制!使用 Argument Dependent Lookup (en.wikipedia.org/wiki/Argument_dependent_name_lookup),当使用类的对象时,与类在同一命名空间中的每个函数都被视为候选函数。因此萨特和亚历山德雷斯库的规则。
  • @steve @thehouse 命名空间是一种简单的冲突避免机制 - 如果您以这种方式设计软件。
  • @Neil:是的,如果他们在没有完全限定的情况下调用该函数,则将模板参数类型的对象作为参数传递,该对象恰好是命名空间中的 UDT,其中包含与同名,那么它可能不起作用。因此,编写这些模板函数的人可能低估了他们的模板参数类型的要求。没有真正的选择可以忽略这个问题并表现得好像它不会影响你,至少如果你正在编写库和可移植代码,并且想要正确地记录它们。
  • 嗯,众所周知,我编写的代码会被我素未谋面的人调用,所以这是我考虑的事情。许多许多用户将意味着许多许多名称空间。实际上我从来没有发布过 C++ 模板库,这就是为什么我处于“是的,我知道记录这个会有问题”的阶段,而不是“啊哈,我以前做过所有这些并且有答案。这是我使用的规则......”或“是的,我们都注定要失败,正如 Sutter 所说的那样,ADL 被打破了”。
【解决方案3】:

为避免 ADL,您只需要两个命名空间:一个包含所有类,另一个包含所有松散函数。 ADL 绝对不是每个类都有自己的命名空间的好理由。

现在,如果您希望通过 ADL 找到某些函数,您可能需要为此目的创建一个命名空间。但是,您实际上仍然不太可能需要每个类都有一个单独的命名空间来避免 ADL 冲突。

【讨论】:

    【解决方案4】:

    可能不会。见Eric Lippert's post on the subject

    这里有几件事:

    1. Eric Lippert 是一名 C# 设计师,但他所说的糟糕的分层设计也适用于此。
    2. 那篇文章中描述的很多内容都与将类命名为与 C# 中的命名空间相同的东西有关,但许多相同的缺陷也适用于 C++。

    您可以通过使用typedefs 来减轻一些 typedef 的痛苦,但这当然只是一个创可贴。

    【讨论】:

    • Eric Lippert 的建议非常适合 C#,也许一般来说也适用于 .Net,但它并不真正适用于 C++,因为 C++ 命名空间的工作方式不同。在这种情况下,需要 C++ 命名空间来防止 非类 函数不明确。
    • 你仍然不应该让类名和命名空间名相同。您仍然会遇到同样的分辨率问题。我同意非类函数是 C# 不必处理的花絮,但 C# 具有 C++ 也不处理的扩展方法,所以我认为它们甚至在那个部门。无论如何,我同意你的观点,这里有更好的答案。但我不喜欢我自己的足以删除它。
    【解决方案5】:

    这是一篇非常有趣的论文,但考虑到作者,它很有可能会是。但是,我注意到问题主要涉及:

    • typedef,因为他们只引入了别名而不是新类型
    • 模板

    如果我这样做:

    namespace foo
    {
      class Bar;
    
      void copy(const Bar&, Bar&, std::string);
    }
    

    并调用它:

    #include <algorithms>
    
    #include "foo/bar.h"
    
    int main(int argc, char* argv[])
    {
      Bar source; Bar dest;
      std::string parameter;
      copy(source, dest, parameter);
    }
    

    然后它应该选择foo::copy。实际上它会同时考虑foo::copystd::copyfoo::copy 不是模板将被优先考虑。

    【讨论】:

    • 如果泛型函数比非泛型函数更匹配,编译器会很高兴地选择它。在您的情况下,您没问题,但不是因为 foo::copy 不是模板,而是因为允许 std::copy 会导致模棱两可的调用。例如,如果 parameter 是一个字符串文字,则将选择 std::copy,因为它可以直接接受这样的值,而 foo::copy 则需要进行转换。
    • 实际上,通过将parameter 设为字符串文字,std::copy 将不再被考虑,所以你仍然可以。但大体上是成立的。
    猜你喜欢
    • 1970-01-01
    • 2013-06-27
    • 2011-02-26
    • 1970-01-01
    • 2010-12-20
    • 2020-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多