c++ - 让细节命名空间代码看不见 - 优雅
问题描述
假设我正在编写一个仅包含标头或主要包含标头的库,并且具有以下代码:
using my_type = int;
namespace detail {
inline void foo() { my_type x; do_foo_stuff(x); }
}
inline void bar() { do_bar_stuff(); detail::foo(); }
inline void baz(my_type y) { do_baz_stuff(y); detail::foo(); }
我想放在foo()
不同的文件中。动机是我有很多这样的详细和不详细的函数,我希望带有我的公共 API 的标头不会被 中出现的内容弄乱detail
,并且不打算直接使用。
问题是 - 这样做的惯用方法是什么?
我不能只在我的公共标头末尾
detail::
包含一个带有代码的文件——因为声明需要在使用它们时进行。我不能只在我的公共标头的开头
detail::
包含一个带有代码的文件——因为它们依赖于一些公共定义,例如类型和常量。让我们假设它们不依赖任何函数。
所以它不能是这两个选项之一。
解决方案
因为通常头文件包含声明,而源文件包含实现,所以人们认为头文件是接口、公共 api,而源文件包含实现细节。在库中,用户(库的消费者)看不到源文件的内容,但可以看到标题,这一事实进一步强化了这一点。
然而这是错误的:
- 标头确实包含定义(模板、内联函数和变量),
- 标头包含私有类成员的声明和具有内部链接的符号(静态非本地和匿名命名空间)。这显然不是 API 的一部分。
- 一个库将公开一个符号(类、函数),即使它仅在内部使用并且不是 API 的一部分(除非它仅在一个 CU 上使用并通过内部链接制作)
- 库标头会将其使用的所有标头引入消费者代码,即使库 API 不需要其中一些标头。这将在用户代码中带来不需要的符号,使命名空间变得混乱。
头文件和源文件的分离不是在公共接口/实现屏障上完成的。这种代码分离只是 C++ 及其 C 遗产的设计方式的产物。C++ 不需要多通道编译器,因此它需要在使用前声明和一个定义。所以标题是一个解决方案。它们不是 API 规范。
所以不幸的结论是 C++ 没有 API / 实现分离,尝试使用标头是不成功的。
现在回答您的问题:我所知道的惯用方式确实是使用details
orimpl
命名空间。据了解,以这种方式命名的命名空间包含库实现细节,不应在用户代码中使用。我个人不会改变你最初的设计。
C++20 终于引入了模块,afaik 解决了这个问题。现在,我们将在消费者和公共 API 中看不到的内部符号明确区分开来。
推荐阅读
- macos - 为什么可以覆盖某些静态库中的符号而不能覆盖其他库中的符号?
- ios - 删除具有重复键的对象
- javascript - 如何设置依赖关系以及如何编译 npm 模块?
- python - TypeError:函数最多接受 4 个参数(给定 6 个)
- c - 是否可以在对宏进行字符串化之前处理数学计算?
- swift - RxSwift - 仅在条件下连接
- continuous-integration - VSTS CICD Build pipline 在将最新到期日期的新 .pfx 文件添加到 repo 后失败
- mysql - 为什么需要存在表才能运行 php artisan 命令 Laravel?
- python - 将稀疏的 IndexedSlices 转换为形状未知的密集张量
- amazon-web-services - AWS EC2 实例不会加载 Kibana