【问题标题】:Namespaces and packaging in C++C++ 中的命名空间和打包
【发布时间】:2014-04-25 20:59:18
【问题描述】:

在打包代码中使用命名空间的最佳/最干净的方法是什么?

例如在像 boost 这样的库中,似乎有非常有条理的命名空间管理,使用了一些允许消除名称歧义的技术。然而,重要的是,人们不会看到太多类似

的代码
typedef namespace1::namespace2::sth_else::a_class<namespace3::namespace4::b_class> type;

通常,没有太多的跨命名空间,这表明良好的架构以及良好的命名空间管理。问题是:什么是好的命名空间管理?

假设我们有这样的文件结构:

component1/...  (depends on reusable_if)
component2/...  (depends directly on reusable_if and on component 1)
reusable/
reusable/some_part/
reusable/some_part/...
reusable/some_other_part/
reusable/some_other_part/...
reusable/SthThatUsesBothReusableParts.h   (implements reusable_if/ISth.h)
reusable/SthThatUsesBothReusableParts.cpp (implements reusable_if/ISth.h)
reusable_if/
reusable_if/ISth.h   (pure abstract class)
reusable_if/ISthElse.h (pure abstract class)
main.cpp (e.g. instantiates SthThatUsesBothReusableParts and passes to component1/2)

之所以有 reusable_if/ 文件夹是因为 component1 和 component2 都想重用相同的接口(因此它们都没有“独占”这些接口)。此外,假设该项目确实非常大,并且需要为每个文件夹中的类提供适当的命名空间。

您将如何在这样的项目中应用命名空间? 假设我在命名空间::reusable 中声明了所有可重用/ 类。我应该将 reusable_if 中的接口放入命名空间::reusable 还是::reusable_if?或者因为它被component1和component2使用,所以可能没有?

component1 和 component2 中的命名空间呢?有什么要记住的吗? 关键字using 怎么样?假设我决定添加这个 ::reusable_if 命名空间。如果using ... 放在命名空间::component1::component2 中,我可以将using reusable_if 放入component1 和component2 的头文件中吗?

我愿意接受任何建议,包括与上述示例不一定相关的建议。

【问题讨论】:

  • 通常保持命名空间层次结构尽可能平坦。
  • @g-makulik 但不要比避免名称冲突所必需的更扁平

标签: c++ architecture namespaces inversion-of-control packaging


【解决方案1】:

个人观点免责声明。您的问题基本上要求主观答案,并且可能会对此关闭,但我会试一试。


命名空间主要用于避免标识符冲突。有“你的”命名空间(mylib::)和其他所有人的命名空间(std::boost::icu::、...),这就是应该采用的命名空间。

将您的项目(如“您的团队的项目”)细分为子命名空间几乎没有什么好处,除非您遇到标识符冲突的问题——在这种情况下,您应该重新考虑调用课程XY 的策略。 ;-)

huge 库中有些不同,比如 Boost。它们实际上由许多不同的项目组成,由独立的团队维护,因此如果将它们都归为boost::,则会出现项目特定标识符相互冲突的问题(并且冲突可能不会出现在临时测试中)。

如果您不再将boost::filesystem 视为“子命名空间”,而是将boost:: 视为单个项目filesystem::thread::program_options:: 等的“身份包装”,让它们看起来更“Boost-ish”,图片变得更清晰。

【讨论】:

  • 好点。我有时使用名称空间来删除​​名称的一部分。也就是说,我通常不会使用 typedef xyz::abcde_callback_type 来编写代码以使用 xyz::callback::abcde_type。你认为这是一种不好的做法吗?
  • @utnapistim:嗯……没有上下文的难题。该名称可能在没有名称空间的情况下使用 (using namespace xyz::callback;)。那它还会有表现力吗?如果它为我提供了可能需要的关键类型信息,即在使用点(而不是“隐藏在源中的其他地方”),我很乐意接受更长的类型名。因此,我认为typedef 是不好的做法,唯一的例外是可移植性类型包装和函数指针。
  • 我认为会是(~using namespace xyz::callback 时富有表现力)。考虑 std 库的 lambda 实现中的类似情况:您有 _1, _2, ... _?? 用于未绑定的参数绑定,您可以编写 using namespace std::placeholders; 而不会失去(很多)可读性。
【解决方案2】:

这是我在项目中使用的。我的主要规则是每个目录都是一个命名空间,每个文件都是一个类,除了极少数例外(有时我将帮助函数分组到命名空间 detail 的子目录中,但没有另一个嵌套命名空间)。

  1. 将您的整个项目保存在以您的项目命名的单个顶级命名空间中。

  2. 将每个实用程序组件保留在顶级命名空间内,但位于单独的目录中。这是我唯一一次不让我的命名空间与我的目录树重叠。

  3. 将项目中每个可独立发布的组件保存在以组件命名的嵌套命名空间内。为方便起见,请提供一个以您的组件命名的标头,并在与您的命名空间对应的目录中或直接在项目的顶级目录中包含整个组件接口。

  4. 将每个组件的实现保留在嵌套命名空间detail 中。与类相反,命名空间不支持private 成员的语言,但Boost 中的约定是命名空间detail 不应由用户代码直接调用。

除了project::component::detail::function()project:::component::class.member() 之外,不需要进一步的嵌套。如果您提供促进 ADL 的完整接口,您可以在项目中将组件函数重用为 function(x),用于类型为 project::component::class 的变量 x,而不必担心名称冲突。

请注意,用鲍勃叔叔的话来说:“重用单元就是发布单元”。每个组件都应该提供一堆连贯且相互依赖的类和函数。特别是,它应该为该组件提供一个完整的界面。 C++ 语言将通过 argument-dependent-lookup (ADL) 来支持这一点。请参阅 Herb Sutter 的旧专栏“Namespaces and the Interface Principle”。

reuse_if 的存在以及componentreusable 的存在可能是代码异味,除非您在 cmets 中提到的注意事项确实适用。组件应该是发布单元。如果您可以独立重用一段代码,请将其作为单独的组件。如果代码依赖于另一部分,请将其与其他代码一起发布。有关这些依赖关系,请参阅 Sutter 的专栏。

【讨论】:

  • 非常有用的答案,它正是针对我想知道的。但是,关于您对 reusable_if 的评论:我不同意 - 我不能与 component1 一起发布“可重用”及其接口,因为 component2 也想使用它。我完全按照您的建议做了 - 我确定了一段重用的(在组件 1 和组件 2 之间)代码(可重用实现的接口)并将其设为单独的组件 (reusable_if)。
  • @Andrew 那么这可能不是什么味道,但最好在文档中明确说明这些依赖关系!
  • 在这种情况下 - 您会将 reusable_if 中的接口放入任何命名空间还是不放入任何命名空间(也要考虑 ADL)?
  • @Andrew 可能在一个命名空间中,但在不同的标头中,因此客户端只能包含他们实际依赖的内容。另请参阅 Scott Meyers 的旧 column
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-08
  • 2018-02-22
  • 2011-07-01
  • 2015-02-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多