C++ 优雅地将详细的命名空间代码从视线中移开

C++ 优雅地将详细的命名空间代码从视线中移开,c++,idioms,forward-declaration,C++,Idioms,Forward Declaration,假设我正在编写一个仅标题或以标题为主的库,并具有以下代码: 使用my_type=int; 名称空间详细信息{ 内联void foo(){my_type x;do_foo_stuff(x);} } 内联void bar(){do_bar_stuff();detail::foo();} 内联void baz(my_type y){do_baz_stuff(y);detail::foo();} 我想将foo()放在另一个文件中。其动机是我有很多这样的详细和不详细的函数,我希望带有公共API的标题不会

假设我正在编写一个仅标题或以标题为主的库,并具有以下代码:

使用my_type=int;
名称空间详细信息{
内联void foo(){my_type x;do_foo_stuff(x);}
}
内联void bar(){do_bar_stuff();detail::foo();}
内联void baz(my_type y){do_baz_stuff(y);detail::foo();}
我想将
foo()
放在另一个文件中。其动机是我有很多这样的详细和不详细的函数,我希望带有公共API的标题不会被
detail
中出现的内容弄乱,并且不打算直接使用

问题是——什么是惯用的方法

  • 我不能只在公共头的末尾包含一个带有
    详细信息::
    代码的文件,因为声明需要在使用时进行

  • 我不能只在公共头的开头包含一个带有
    详细信息::
    代码的文件,因为它们依赖于一些公共定义,例如类型和常量。让我们假设它们不依赖任何函数


因此,它不能是这两个选项之一。

我目前的想法是对同一个头进行文件开头和文件结尾包含,而不是使用两次包含机制为其提供包含保护,第一次包含公开声明,第二次公开定义,例如:

#ifndef MY_DETAIL_HPP_TWICE
#ifndef MY_DETAIL_HPP_ONCE
#define MY_DETAIL_HPP_ONCE
#else
#define DEFINITIONS_VISIBLE
#define MY_DETAIL_HPP_TWICE
#endif // MY_DETAIL_HPP_ONCE

namespace detail {

inline void foo()
#ifndef DEFINITIONS_VISIBLE
;
#else
{ my_type x; do_foo_stuff(x); }
#endif

}
#endif // MY_DETAIL_HPP_TWICE

但这似乎有些做作,我还没有看到它在任何地方使用。

因为通常头包含声明,源文件包含实现。人们认为头是接口、公共api,而源文件包含实现细节。在库中,用户(库的使用者)看不到源文件的内容,但可以看到标题,这一事实进一步强化了这一点

然而,这是错误的:

  • 标题确实包含定义(模板、内联函数和变量)
  • 标头包含私有类成员的声明和具有内部链接的符号(静态非局部名称空间和匿名名称空间)。这显然不是API的一部分
  • 库将公开符号(类、函数),即使它仅在内部使用,并且不是API的一部分(除非它仅在一个CU上使用,并且带有内部链接)
  • 库头将把它使用的所有头都引入使用者代码,即使库API不需要其中的一些头。这将在用户代码中引入不必要的符号,使名称空间变得混乱
头文件和源文件的分离不是在公共接口/实现屏障上完成的。这种代码分离只是C++如何设计C继承的一个伪事实。C++不需要多遍编译器,所以它需要在使用之前声明和一个定义。因此,标题是解决这个问题的一个方法。它们不是API规范

<不幸的结论是C++没有API /实现分离,试图使用标题,否则将不成功。 现在来回答你的问题:我知道的惯用方法确实是使用
details
impl
名称空间。可以理解,以这种方式命名的名称空间包含库实现细节,不应在用户代码中使用。我个人不会改变你最初的设计



C++20最终引入了模块,afaik解决了这个问题。现在我们有了一个清晰的内部符号分离,在消费者和公共API中是看不到的。

或者可能有两个不同的头文件:detail_decl.h和detail_impl.h?@JerryJeremiah:这是可能的。不过,它会有很多代码重复,不是吗?不管怎样,你可以回答这个问题,看看《人民之音》喜欢什么:——)我不喜欢;我不知道这是不是一个好主意-我还没有看到有人做了你想做的事情。。。如果你真的找到了一个你喜欢的解决方案,我会很有兴趣看一看。如果你尽可能地把它做好,而不考虑只做页眉,把只做页眉作为一个可能的改进,那会怎么样?我不认为把它放进编译过的库中会失去太多的可用性,所以我可能有偏见:-)@tedlynmo:我已经承诺使用header,主要是出于各种原因。但一般来说,这是一个公平的评论。如果详细内容依赖于类型和常量的一些公共定义,那么这些类型和常量不应该作为单独的标题包含进来吗?@Bob_uu;:可以;但是,阅读API的用户需要同时查看两个文件—带有
my_type
的文件和带有
bar
baz
和其他函数的文件。您的答案实际上不是答案。我已经在使用一个
detail
子名称空间。这个问题是关于如何以及在何处放置其内容。至于C++ 20个模块——我将无法依赖它们,直到C++ 2020退出并采纳了好几年之后。“我的动机是,我有很多这样的细节和细节之外的函数,我希望我的公共API的标题不会被细节中出现的内容弄乱,也不是为了直接使用。“我的回答是,你不能有一个带有公共API的头,所以保持原样就行了。不管怎样,那是我的两分钱。这只是一个你可能会考虑的POV。至于模块,是的,我知道实现还没有在这里,这就是为什么他们的提及与答案的其余部分分开,更多的是一个脚注。我觉得值得一提。