安全使用C ++库界面中的容器[英] Safely use containers in C++ library interface

本文是小编为大家收集整理的关于安全使用C ++库界面中的容器的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

设计一个C ++库时,我读到在公共界面中包括std::vector等标准库容器是不好的做法(例如,请参见e.g. 在dll导出函数中使用std :: vector的含义).

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

处理图书馆界面中的容器的最佳做法是什么?我可以用作"胶水"的小容器实现(最好只有一个或两个我可以丢弃的文件,带有宽松的许可证)?或者甚至有一种使std::vector等制造的方法.

推荐答案

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

由于ABI不是标准化的,因此您可能会遇到各种麻烦.通常,您必须为每个受支持的编译器版本提供单独的二进制文件,以使其正常工作.获得真正便携式DLL的唯一方法是坚持使用普通的C接口.这通常会导致诸如 com ,因为您必须确保所有分配和匹配的交易都发生在相同的模块,没有实际对象布局的细节暴露于用户.

其他推荐答案

您可以实现模板功能.这有两个优点:

  1. 它使您的用户可以决定要在接口中使用哪些类型的容器.
  2. 它使您不必担心ABI兼容性,因为您的库中没有代码,当用户调用函数时,它将进行实例化.

例如,将其放在标题文件中:

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 ::功能或其他任何东西使回调更加幻想,但这可能会击败其他一些目标.

其他推荐答案

tl; dr 如果您为各种支持集的源代码或编译二进制文件(ABI +标准库实施),没有问题.

通常,后者被视为麻烦(有原因),因此是指南.


我相信竭尽所能挥手的准则...我鼓励您做同样的事情.

该准则源自具有ABI兼容性的问题:ABI是一组复杂的规格,该规范定义了编译库的精确接口.它包括:

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

有关更多详细信息,请检查例如 itanium abi .与具有非常简单的ABI的C相反,C ++的表面积要复杂得多……因此,为其创建了许多不同的ABI.

在ABI兼容性之上,标准库实施也存在问题.大多数编译器都具有自己对标准库的实施,这些实现与彼此不相容(例如,即使所有人都实现了相同的接口和保证,它们并不代表A std::vector).

结果,如果两者都针对同一ABI和兼容的标准库实现,则只能将编译的二进制(可执行文件或库)与另一个编译的二进制文件匹配.

.

欢呼:如果您分发源代码并让客户端编译.

本文地址:https://www.itbaoku.cn/post/1937833.html

问题描述

When designing a C++ library, I read it is bad practice to include standard library containers like std::vector in the public interface (see e.g. Implications of using std::vector in a dll exported function).

What if I want to expose a function that takes or returns a list of objects? I could use a simple array, but then I would have to add a count parameter, which makes the interface more cumbersome and less safe. Also it wouldn't help much if I wanted to use a map, for example. I guess libraries like Qt define their own containers which are safe to export, but I'd rather not add Qt as a dependency, and I don't want to roll my own containers.

What's the best practice to deal with containers in the library interface? Is there maybe a tiny container implementation (preferably just one or two files I can drop in, with a permissive license) that I can use as "glue"? Or is there even a way to make std::vector etc. safe across .DLL/.so boundaries and with different compilers?

推荐答案

Actually this is not only true for STL containers but applies to pretty much any C++ type (in particular also all other standard library types).

Since the ABI is not standardized you can run into all kinds of trouble. Usually you have to provide separate binaries for each supported compiler version to make it work. The only way to get a truly portable DLL is to stick with a plain C interface. This usually leads to something like COM, since you have to ensure that all allocations and matching deallocations happen in the same module and that no details of the actual object layout are exposed to the user.

其他推荐答案

You can implement a template function. This has two advantages:

  1. It lets your users decide what sorts of containers they want to use with your interface.
  2. It frees you from having to worry about ABI compatibility, because there is no code in your library, it will be instantiated when the user invokes the function.

For example, put this in your header file:

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
}

Then your users can invoke foo with any container type, even ones they invented that you don't know about.

One downside is that you'll need to expose the implementation code, at least for foo.

Edit: you also said you might want to return a container. Consider alternatives like a callback function, as in the gold old days in 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;
}

That's a pretty old school "C" way, but it gives you a stable interface and is pretty usable by basically any caller (even actual C code with minor changes). The two quirks are the void* userData to let the user jam some context in there (say if they want to invoke a member function) and the bool return type to let the callback tell you to stop. You can make the callback a lot fancier with std::function or whatever, but that might defeat some of your other goals.

其他推荐答案

TL;DR There is no issue if you distribute either the source code or compiled binaries for the various supported sets of (ABI + Standard Library implementation).

In general, the latter is seen as cumbersome (with reasons), thus the guideline.


I trust hand-waving guidelines about as far as I can throw them... and I encourage you to do the same.

This guidelines originates from an issue with ABI compatibility: the ABI is a complex set of specifications that defines the exact interface of a compiled library. It is includes notably:

  • the memory layout of structures
  • the name mangling of functions
  • the calling conventions of functions
  • the handling of exception, runtime type information, ...
  • ...

For more details, check for example the Itanium ABI. Contrary to C which has a very simple ABI, C++ has a much more complicated surface area... and therefore many different ABIs were created for it.

On top of ABI compatibility, there is also an issue with Standard Library Implementation. Most compilers come with their own implementation of the Standard Library, and these implementations are incompatible with each others (they do not, for example, represent a std::vector the same way, even though all implement the same interface and guarantees).

As a result, a compiled binary (executable or library) may only be mixed and matched with another compiled binary if both were compiled against the same ABI and with compatible versions of a Standard Library implementation.

Cheers: no issue if you distribute source code and let the client compile.