16

在准备一个库(我们称之为 libfoo)时,我发现自己面临以下两难境地:我是否将其编写为带有 C 包装器的 C++ 库:

namespace Foo {
  class Bar {
    ...
  };
}

/* Separate C header. #ifdef __cplusplus omitted for brevity. */
extern "C" {
  typedef void *FooBar;
  FooBar* foo_bar_new() { return new Foo::Bar; }
  void foo_bar_delete(FooBar *bar) { delete bar; }
}

还是将其编写为带有 C++ 包装器的 C 库更好:

/* foo/bar.h. Again, #ifdef __cplusplus stuff omitted. */

typedef struct {
  /* ... */
} FooBar;

void foo_bar_init(FooBar *self) { /* ... */ }
void foo_bar_deinit(FooBar *self) { /* ... */ }

/* foo/bar.hpp */

namespace Foo {
  class Bar {
    /* ... */
    FooBar self;
  }

  Bar::Bar() {
    foo_bar_init(&self);
  }

  Bar::~Bar() {
    foo_bar_deinit(&self);
  }
}

你更喜欢哪个?为什么?我喜欢后者,因为这意味着我不必担心我的 C 函数会意外出现异常,而且我更喜欢 C 作为一门语言,因为我觉得它是一个较小的语义雷区。其他人怎么想?

编辑:这么多好的答案。谢谢大家。很遗憾,我只能接受一个。

4

9 回答 9

19

小点:

当您编写 C 库时,它在任何地方都很有用 - 在 C、C++(带有包装器)和许多其他语言(如 Python、使用绑定的 Java 等)中,最重要的是它只需要 C 运行时。

当你写 C++ wrapper 时,你也需要写一个 C wrapper,但它并不像你想象的那么简单,例如:

c_api.h:

extern "C" {
  typedef void *Foo;
  Foo create_foo();
}

c_api.cpp:

void *create_foo() 
{
    return new foo::Foo();
}

怎么了?它可能会抛出!并且程序将崩溃,因为 C 没有堆栈展开语义。所以你需要类似的东西:

void *create_foo() 
{
    try {
       return new foo::Foo();
    }
    catch(...) { return 0; }
}

这适用于每个C++ api 函数。

所以我认为编写一个 C 库并提供一个单独的C++ 包装器是更好的解决方案。

它也不需要与 C++ 运行时库链接。

于 2010-10-12T07:37:31.887 回答
9

用您喜欢编写库的语言编写库。从技术上讲,您使用哪种包装方式并不重要。尽管一些 C 项目可能旨在排除不是 C 的库,而 C++ 项目排除用 C 编写的库会很奇怪,但这主要是一种哲学上的反对意见,而不是实际的反对意见。

将 C 包装在 C++ 包装器中可能会导致包装器稍大,但对于 C 程序员来说更容易接受。

请注意,如果您正在分发二进制文件,则 C 的简单性是有利的。

于 2010-10-12T07:05:55.350 回答
5

如果您更喜欢用 C 编写,为什么需要 C++ 包装器?C++ 客户端可以使用 C 风格的 API 接口。另一方面,您更喜欢 C++,有必要为 C 客户端提供 C 包装器。

于 2010-10-12T07:07:16.823 回答
4

如果您的 lib 必须作为二进制 + 标头分发(而不是发送源代码),您会发现C API 具有更普遍的可链接性,因为 C 通常是任何平台上最小的通用 API。

这就是为什么我通常不得不为过去十年所做的需要 API 的项目制作带有内联 C++ 包装器的 C API。由于这些程序都是用 C++ 编写的,这意味着我必须围绕 C++ 代码制作一个 C 包装 API,只是为了在它周围放置另一个包装 C++ API

于 2010-10-12T07:26:21.057 回答
3

假设编译没有链接时优化,C 编译器不能内联包装函数,因为它不知道如何处理 C++ 调用 - 但 C++ 编译器可以轻松内联 C 调用。

因此,为 C 库创建 C++ 包装器可能是个好主意,而不是相反。

于 2010-10-12T07:27:01.273 回答
2

我个人更喜欢使用 C++ 并将它包装到 C 中。但事实上这是一个品味问题,你必须自己决定你喜欢它。如果你觉得用 C 编写库更舒服,那就去用 C++ 包装它。

关于异常:您可以在为 C 包装的每个函数中捕获它们并为它们返回错误代码,例如拥有一个自己的异常类,该异常类已经有一个数字错误代码值,您可以将其返回给您的 C 函数,其他可能有被任何其他库抛出的东西可以被翻译成别的东西,但是无论如何你应该早点抓住它们。

于 2010-10-12T07:08:10.087 回答
2

如果您对用 C 编写库感到满意,那就去做吧。它将作为 C 库更具可移植性,并且没有您提到的异常问题。从 C++ 库开始并将其包装在 C 中是不常见的。

于 2010-10-12T07:10:59.503 回答
2

它还很大程度上取决于您计划在库中使用什么。如果它反过来可以从其他 C++ 库中受益匪浅,那么请使用 C++。

也可以说,如果你的库非常大(在内部,不一定是 API 方面),那么在 C++ 中实现它会更容易。(这不是我的一杯茶,我更喜欢 C,但有些人对 C++ 发誓。)

还要记住,C++ 使用一个非常需要操作系统的运行时来支持异常。

如果您设想将您的库用作操作系统的基础,或者在没有操作系统的环境中使用,您要么必须知道如何禁用异常支持,避免大量(全部?) STL 并提供您自己的分配器和释放器。这并非不可能,但你需要确切地知道你在做什么。

C更适合那些低级的东西。

于 2010-10-12T08:30:15.427 回答
2

我个人更喜欢用 C++ 编写它,然后使用包装器公开 C 接口。主要是因为我宁愿用正确的 OO 语言编写。我也会使用 OO 风格的 C 包装器,就像我在这篇文章中概述的那样,我在这篇文章为面向对象的 C++ 代码开发 C 包装器 API 中写了一个非常详细的解释,说明从 C 调用 OO C++ 需要什么

于 2010-10-12T09:01:29.253 回答