c++ 优雅地将detail-namespace代码隐藏起来

qlzsbp2j  于 2023-06-07  发布在  其他
关注(0)|答案(3)|浏览(135)

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

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中出现的内容弄得一团糟,并且不打算直接使用。
问题是,什么是一个惯用的方法来做到这一点?

  • 我不能只在我的公共头的 end 包含一个带有detail::代码的文件--因为声明需要在使用它们的时候进行。
  • 我不能只在我的公共头的 * 开头 * 包含一个带有detail::代码的文件-因为它们依赖于一些公共定义,例如。类型和常量。让我们假设它们不依赖于任何 * 函数 *。

所以不可能是这两个选项之一。

ffscu2ro

ffscu2ro1#

因为通常头文件包含声明,而源文件包含实现,所以人们认为头文件是接口,公共API,而源文件包含实现细节。在库中,用户(库的消费者)看不到源文件的内容,但可以看到头文件,这一事实进一步加强了这一点。
然而,这是错误的:

  • 头文件确实包含定义(模板、内联函数和变量),
  • 头部包含私有类成员的声明和具有内部链接的符号(静态非局部变量和匿名命名空间)。这显然不是API的一部分。
  • 一个库将公开一个符号(类,函数),即使它是内部专用的,不是API的一部分(除非它只在一个CU上使用,并与内部链接)
  • 一个库头将把它所使用的所有头引入消费者代码,即使那些头中的一些头对于库API是不需要的。这将在用户代码中带来不需要的符号,使名称空间变得混乱。

头文件和源文件的分离不是在公共接口/实现屏障上完成的。这种代码分离只是C及其C遗产的设计方式的一个产物。C不需要多遍编译器,所以它需要在使用前声明和一个定义。所以header是一个解决方案。它们不是API规范。
因此,不幸的结论是,C没有API /实现分离,试图使用头将是不成功的。
现在回答你的问题:我知道的惯用方法确实是使用detailsimpl名称空间。可以理解,以这种方式命名的名称空间包含库实现细节,不应该在用户代码中使用。我个人不会改变你最初的设计。
C
20终于引入了模块,这解决了这个问题。现在我们有了一个清晰的内部符号分离,这些符号在消费者和公共API中是看不到的。

vatpfxk5

vatpfxk52#

如果你不要求“public API”实现在main header中,并且只对函数签名感到满意,你可以将声明拆分到main header中,将定义拆分到detail header中,如下所示:

//header.hpp
using my_type = int;

inline my_type bar();
inline my_type baz(my_type y);

#include "detail.hpp"
// detail.hpp
#include <cstdlib>
namespace detail {
    inline my_type foo(my_type i) { return std::rand() + i; }
}

inline my_type bar() { return detail::foo(3); }
inline my_type baz(my_type y) { return detail::foo(y); }
// main.cpp
#include "header.hpp"
#include <iostream>

int main() {
    std::cout << bar() << " "<< baz(4) << std::endl;
    return 0;
}

使用gcc 11.3编译

$ g++ -std=gnu++14 main.cpp -Wall -Werror -Wpedantic && ./a.out
1804289386 846930890
rkkpypqq

rkkpypqq3#

我目前的想法是对同一个头文件进行文件开始和文件结束包含,而不是给它一个包含保护,使用两次包含机制,第一次包含暴露声明,第二次暴露定义,例如。比如:

#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

但这看起来很做作,我还没看到有人用过。

相关问题