在C ++库接口中安全地使用容器

时间:2014-01-17 12:07:43

标签: c++ containers api-design

在设计C ++库时,我认为在公共界面中包含std::vector等标准库容器是不好的做法(参见例如Implications of using std::vector in a dll exported function)。

如果我想公开一个获取或返回对象列表的函数,该怎么办?我可以使用一个简单的数组,但是我必须添加一个count参数,这会使界面更麻烦,更不安全。例如,如果我想使用map,它也无济于事。我想像Qt这样的库定义了自己的容器,这些容器可以安全地导出,但我宁愿不将Qt作为依赖项添加,而且我不想滚动自己的容器。

在库界面中处理容器的最佳做法是什么?是否可能有一个小容器实现(最好只有一两个文件,我可以使用许可许可证),我可以用作“粘合剂”?或者甚至有一种方法可以使std::vector等在.DLL / .so边界和不同编译器之间安全吗?

4 个答案:

答案 0 :(得分:4)

实际上,这不仅适用于STL容器,而且几乎适用于任何C ++类型(特别是所有其他标准库类型)。

由于ABI未标准化,您可能遇到各种麻烦。通常,您必须为每个受支持的编译器版本提供单独的二进制文件,以使其工作。获得真正可移植DLL的唯一方法是坚持使用普通的C接口。这通常会导致类似COM的内容,因为您必须确保所有分配和匹配的解除分配都发生在同一个模块中,并且不会向用户公开实际对象布局的详细信息。

答案 1 :(得分:3)

您可以实现模板功能。这有两个好处:

  1. 它允许您的用户决定他们希望在您的界面中使用哪种容器。
  2. 它让您不必担心ABI兼容性,因为您的库中没有代码,当用户调用该函数时,它将被实例化。
  3. 例如,将其放在头文件中:

    template <typename Iterator>
    void foo(Iterator begin, Iterator end)
    {
      for (Iterator it = begin; it != end; ++it)
        bar(*it); // a function in your library, whose ABI doesn't depend on any container
    }
    

    然后,您的用户可以使用任何容器类型调用foo,甚至是他们发明的您不知道的容器类型。

    一个缺点是你需要暴露实现代码,至少对foo来说。

    编辑:您还说过您可能想要返回一个容器。考虑像回调函数这样的替代方法,就像C:

    中的黄金时代一样
    typedef bool(*Callback)(int value, void* userData);
    void getElements(Callback cb, void* userData) // implementation in .cpp file, not header
    {
      for (int value : internalContainer)
        if (!cb(value, userData))
          break;
    }
    

    这是一个相当古老的学校“C”方式,但它为您提供了一个稳定的界面,基本上任何调用者都可以使用(即使是实际的C代码也有微小的变化)。两个怪癖是void * userData让用户在那里堵塞一些上下文(比如他们想要调用成员函数)和bool返回类型让回调告诉你停止。您可以使用std :: function或其他任何方式使回调变得更加漂亮,但这可能会破坏您的其他一些目标。

答案 2 :(得分:3)

TL; DR 如果为各种受支持的(ABI +标准库实现)集合分发源代码或已编译的二进制文件,则没有问题。

一般来说,后者被视为繁琐(有原因),因此是指南。


我信任挥手指导,尽我可以扔掉它们......我鼓励你这样做。

本指南源于ABI兼容性问题:ABI是一组复杂的规范,用于定义已编译库的精确接口。它特别包括:

  • 结构的内存布局
  • 函数名称错误
  • 函数的调用约定
  • 处理异常,运行时类型信息,......
  • ...

有关详情,请查看Itanium ABI。与具有非常简单的ABI的C相反,C ++具有更复杂的表面区域......因此为它创建了许多不同的ABI。

除了ABI兼容性之外,标准库实施也存在问题。大多数编译器都有自己的标准库实现,并且这些实现彼此不兼容(例如,它们不会以相同的方式表示std::vector,即使所有实现都实现相同的接口和保证)。

因此,如果编译的二进制文件(可执行文件或库)都是针对相同的ABI和标准库实现的兼容版本编译的,则它们只能与另一个编译的二进制文件混合并匹配。

干杯:如果您分发源代码并让客户端编译,则没有问题。

答案 3 :(得分:0)

如果您使用的是C ++ 11,则可以使用cppcomponents。 https://github.com/jbandela/cppcomponents

这将允许您使用std :: vector作为参数或返回使用不同编译器或标准库创建的Dll /或.so文件的值。请查看我对类似问题的回答,例如Passing reference to STL vector over dll boundary

请注意,您需要在CPPCOMPONENTS_REGISTER(ImplementFiles)语句后添加CPPCOMPONENTS_DEFINE_FACTORY()