C++头文件包装库

C++ header file wrapper library

本文关键字:包装 文件包 文件 C++      更新时间:2023-10-16

我正在尝试模仿我最近看到的项目模式,以提供"C++二进制兼容"的API。 我通过向纯 C 接口提供一个C++头文件包装器来做到这一点。 (这与 C++ OpenCL 包装文件 cl.hpp 使用纯 C OpenCL 接口所做的技巧相同。

下面是一个精简的示例。

foo.h是纯 C 接口:

typdef void *foo_t;
extern "C" DLLEXPORT foo_t foo_open(const char *);
extern "C" DLLEXPORT int foo_compute(foo_t, const char *buf, int blen);
extern "C" DLLEXPORT void foo_close(foo_t);

foo.hpp是也与标头一起分发的C++包装器:

struct Foo {
  foo_t handle;
  Foo() { handle = foo_open(...); }
  ~Foo() { foo_close(handle); }
  std::string compute() {
    char buf[512];
    foo_compute(handle, buf, sizeof(buf))
    return std::string(buf);
  }
}

Joe 用户获得foo.hfoo.hppfoo.dll(带有导出库)。 Foo.dll 本可以使用 MSVC2012、MSVC2013 或 mingw 进行编译,但 Joe User 仍然可以使用 MSVC2015。 这一切都解决了,因为非二进制可移植C++包装器实现位于公共标头中。

问题是:我的包装器有点复杂,我想简化它通过向下移动一些较大方法的定义。 即我希望C++接口简洁并与实现分开列出(可以在下面)。

// INTERFACE: keep it concise
struct Foo {
  foo_t handle;
  Foo() { handle = foo_open(...) }
  ~Foo() { foo_close(handle); }
  std::string compute(); // keep it concise
}
// IMPLEMENTATIONS: most folks don't need to read this
std::string Foo::compute() {
   char buf[512];
   foo_compute(handle, buf, sizeof(buf))
   return std::string(buf);
}

问题是,如果多个对象文件包含foo.hpp(它们会),我最终会得到多个Foo::compute定义,因为每个对象文件都会标记一个副本,而不是使用内联类定义的版本。

我建模cl.hpp只是内联了所有定义,所以这没有帮助。 我环顾互联网,但找不到我想要做什么的好例子,但也许我使用了错误的命名法。

如果这些只是函数,我只需将它们全部标记为static,它们就不会逃脱它们的编译单元。 最糟糕的情况是,我想我可以使用静态辅助函数来解决这个问题。

有什么想法吗?

谢谢!

引用:[1] 这略有关系。 https://chadaustin.me/cppinterface.html

对于包装器,将成员函数保持inline是有意义的。这可以通过在类中定义它们来完成(如在已发布代码的第一个片段),或者在类外部定义它们并显式声明它们inline例如在您的示例中:

// IMPLEMENTATIONS: most folks don't need to read this
inline std::string Foo::compute() {
    char buf[512];
    foo_compute(handle, buf, sizeof(buf));
    return std::string(buf);
}

声明为 inline 的函数可以在头文件中定义,该头文件在多个源文件(也称为翻译单元)中 #include,而不会导致多个定义编译器错误。引用内联说明符的文档:

只要每个定义出现在不同的翻译单元中,程序中的内联函数就可以有多个定义。例如,可以在多个源文件中 #include 的头文件中定义内联函数。

编译器是否实际内联函数

(在每次出现时"扩展"内联代码与生成实际函数调用的意义上)对于声明为 inline 的函数的标准所保证的行为无关紧要,并且不会影响/更改。从同一链接页面:

由于关键字 inline 的这种含义是非绑定的,因此编译器可以自由地对任何未标记为内联的函数使用内联替换,并且可以自由生成对标记为内联的任何函数的函数调用。这些选择不会更改上面列出的有关多个定义和共享静态数据的规则。