【问题标题】:What is best practice for C++ Public API?C++ 公共 API 的最佳实践是什么?
【发布时间】:2009-11-09 16:58:02
【问题描述】:

C++ 公共 API 的最佳实践是什么?

我正在开发一个具有多个命名空间的 C++ 项目,每个命名空间都有多个对象。一些对象具有相同的名称,但位于不同的命名空间中。目前,每个对象都有自己的 .cpp 文件和 .h 文件。我不知道如何措辞...创建第二个 .h 文件以仅公开公共 API 是否合适?它们应该是每个命名空间或每个对象或某个其他范围的 .h 文件吗?为 C++ 库创建公共 API 的最佳实践是什么?

感谢您的帮助, 陈兹

【问题讨论】:

    标签: c++ api


    【解决方案1】:

    有时在每个 .cpp 和 .h 文件对中拥有一个类并将命名空间层次结构作为目录层次结构有时会很方便。
    例如,如果你有这个类:

    namespace stuff {
      namespace important {
        class SecretPassword 
        {
           ...
        };
      }
    }
    

    那么它将在两个文件中:

    /stuff/important/SecretPassword.cpp
    /stuff/important/SecretPassword.h
    

    另一种可能的布局可能是:

    /src/stuff/important/SecretPassword.cpp
    /include/stuff/important/SecretPassword.h
    

    【讨论】:

    • 但是当把所有东西放在一个 .so 库中时,我是否分发了一个深度包含目录结构?陈兹
    • @Crazy Chenz,是的,这是一种可能性。例如,这就是它在 Linux 内核中的完成方式,并且在某种程度上也是如此。
    【解决方案2】:

    生日,

    一个建议是看一看C++ 的Handle-Body 习语,有时也被称为柴郡猫。这是包含成语的James Coplien's original paper

    这是一种众所周知的将公共 API 与实现分离的方法。

    HTH

    【讨论】:

    • 这个成语的另一个名字(我认为它变得更流行了,但我可能错了)是“pimpl idiom”——指向实现的指针。
    • @michael,你是对的。该名称也已添加,但最近 iirc。 handle/body 是 Cope 在 1991 年出版的《高级 C++ 编程风格和习语》一书中给它起的原始名称 amazon.com/dp/0201548550>
    【解决方案3】:

    我会说最好由你决定,这是“图书馆”的类型。

    您的 API 是否提供一个“操作”?还是只处理一种抽象的“数据类型”?这方面的例子是 zlib 和 libpng。两者都只有一个标头,提供执行库所需的一切。

    如果您的库是一个不相关(甚至相关)类的集合,这些类的目标是否相同,则为每个子集提供其自己的标头。主要的例子就是 boost。

    【讨论】:

      【解决方案4】:

      这是我习惯做的事情:

      “有些对象名称相同,但在不同的命名空间中”

      这就是命名空间存在的原因。

      “创建第二个 .h 文件以仅公开公共 API 是否合适?”

      您始终应该只公开公共 API。但是公开公共 API 意味着什么?如果只是标头,那么由于公共 API 依赖于私有 API,因此私有 API 无论如何都会包含在公共 API 中。公开一个公共 API 用一个宏标记公共函数/类(在 Windows 的情况下将公共函数导出到符号表;并且可能很快就会被 Unix 系统采用)。所以你应该定义一个像 MYLIB_API 或 MYLIB_DECLSPEC 这样的宏,只需检查一些现有的库和 MS declspec 文档。足够了,通常非公共 API 将保存在子目录中,因此它不会涉及库的用户。

      “它们应该是每个命名空间或每个对象或某个其他范围的 .h 文件吗?”

      我更喜欢 Java 风格,每个标头一个 public 类。我发现以这种方式编写的库比那些混合文件和结构名称的库更干净和可读。但是在某些情况下,我会打破这条规则,尤其是在模板方面。在这种情况下,我会给出#warning 消息以不直接包含标头,并在 cmets 中仔细解释发生了什么。

      【讨论】:

      • "但是公开公共 API 意味着什么?"我正在考虑在没有私有方法和变量的情况下复制所有用于分发的标头。
      • 我的意思是......没有他们的私有方法原型和私有变量声明。
      • 这在海事组织中没有任何意义。事实上,这只会让你的库更难理解和维护。如果有人必须扩展您的类,那么他可能希望看到整个类结构,而不仅仅是公共和受保护的成员。类定义一定要放在一个标题中。
      【解决方案5】:

      大反响 LiraNuna。

      您是为应用程序还是库提供 API?

      应用程序 API 通常只提供查询应用程序状态或尝试更改该状态的方法。在这种情况下,您通常会在单独的头文件中拥有单独的接口声明。然后,您的对象将实现此接口来处理 API 请求。

      库通常会公开可重复使用的对象。在这种情况下,一般来说,你的 API 就是头文件中的公共方法。

      【讨论】:

      • 我有一个应该可重用的核心库,然后是一些命名空间,其中包含使用该核心的应用程序的所有对象和助手。核心是我想要“公开”公共 API 的。
      • 在我的发行版中有显示私有方法/变量定义的 .h 文件似乎很奇怪。
      • 好的,所以我的指导方针是 - 尽可能缩小范围。如果我包含 'foo.h',不要让我继承一堆我不关心的函数和对象。考虑 STL - 我 #include 而不是 。只要您的界面完整且定义明确,在客户端代码中包含许多内容比膨胀(恕我直言)更可取。
      • 编译器不能消除臃肿吗?在 gtk 和 Xlib 之类的库中,您可以包含 gtk/gtk.h 或 X11/Xlib.h,仅此而已。他们是否为了更简洁的代码而牺牲了保护?
      • 我想在这些情况下,我可能会更具体,但他们添加了一个包含膨胀的便利头文件,但让我不必知道其他所有内容。
      【解决方案6】:

      我同意文档所说的 - 每个文件一个类。 99.9% 的时间!

      另外,考虑使用什么文件名。在不同的目录中拥有多个同名的标头通常是个坏主意,即使它们包含的类很可能位于不同的命名空间中。

      特别是如果这是一个公共 API,您可能无法控制库的用户定义的包含路径,因此构建可能会找到错误的路径。调整包含路径的顺序绝对不是我推荐的解决方案!

      我们使用Namespace-Class.h 的命名约定来明确标识文件中的类。

      【讨论】:

      • 请问您如何处理长名称空间?假设您有一个类 mylib::graphics::formats::JpegLoader。名称标题 Mylib_Graphics_Formats-JpegLoader.h?使用这样的标题有点令人沮丧。 IMO 避免意外包含的最佳方法是强制您的库名称成为包含路径的一部分,在我们的示例中,包含看起来像 #include 。直到你在 中有“mylib”,一切都会好起来的。
      • 幸运的是我们的命名空间没有嵌套那么深;只有一个命名空间,很少有 2 个,所以还不错。它以何种方式令人沮丧 - #include "Mylib-Graphics-Formats-JpegLoader.h"#include "mylib/graphics/formats/JpegLoader.h" 之间有什么区别?您仍然需要以某种方式输入完整的“路径”吗?
      • 如果你保持与命名空间相关的目录结构,你可能有#include ,而在我这边它会是 。如果您将所有内容都保存在一个目录中,则可以。
      • @doc:哦,哇! :-) 我们使用平面目录树并将“结构”放入文件名中。当您(在您的示例中)有 2 个 JpegLoader.h 文件时,这可以防止歧义。我可以看到您是否强制 API 的用户将他们的包含路径设置为指向 mylib 的位置,那么您就没有歧义了。但是你不能总是保证用户会做正确的事(或者你可以保证他们做错的事!)。
      猜你喜欢
      • 2010-09-13
      • 2020-01-25
      • 2011-06-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-26
      • 2021-05-12
      相关资源
      最近更新 更多