在C++库接口中安全地使用容器
Safely use containers in C++ library interface
在设计C++库时,我了解到在公共接口中包含std::vector
等标准库容器是不好的做法(例如,请参阅在dll导出函数中使用std::vector的含义)。
如果我想公开一个接受或返回对象列表的函数,该怎么办?我可以使用一个简单的数组,但之后我必须添加一个count
参数,这会使接口更加繁琐和不安全。此外,例如,如果我想使用map
,也不会有多大帮助。我想像Qt这样的库定义了自己的容器,这些容器可以安全导出,但我不想将Qt添加为依赖项,也不想滚动自己的容器。
在库接口中处理容器的最佳做法是什么?有没有一个小的容器实现(最好只有一两个文件,我可以放进去,有许可证)可以用作"胶水"?或者,有没有一种方法可以使std::vector
等在.DLL/.so边界上以及使用不同的编译器时都是安全的?
您可以实现一个模板函数。这有两个优点:
- 它让用户决定他们想在界面中使用什么类型的容器
- 它使您不必担心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::函数或其他什么方法让回调变得更花哨,但这可能会破坏你的其他一些目标。
实际上,这不仅适用于STL容器,而且适用于几乎任何C++类型(尤其是所有其他标准库类型)。
由于ABI不是标准化的,你可能会遇到各种各样的麻烦。通常,您必须为每个受支持的编译器版本提供单独的二进制文件才能使其工作。获得真正可移植DLL的唯一方法是使用纯C接口。这通常会导致类似COM的情况,因为您必须确保所有分配和匹配的释放都发生在同一个模块中,并且不会向用户公开实际对象布局的详细信息。
TL;DR如果您为各种受支持的(ABI+标准库实现)集分发源代码或编译的二进制文件,则没有问题。
一般来说,后者被视为繁琐(有原因),因此成为准则。
我相信挥手指导原则,尽我所能。。。我鼓励你们也这样做。
本指南源于ABI兼容性问题:ABI是一组复杂的规范,定义了编译库的精确接口。它主要包括:
- 结构的内存布局
- 函数的名称篡改
- 函数的调用约定
- 异常的处理、运行时类型信息
有关更多详细信息,请查看安腾ABI。与具有非常简单ABI的C相反,C++具有更复杂的表面积。。。因此为其创建了许多不同的ABI
除了ABI兼容性之外,标准库实现也存在问题。大多数编译器都有自己的标准库实现,并且这些实现彼此不兼容(例如,它们不以相同的方式表示std::vector
,即使所有编译器都实现相同的接口和保证)。
因此,一个编译的二进制文件(可执行文件或库)只能与另一个编译过的二进制文件混合和匹配,前提是两者都是根据相同的ABI和标准库实现的兼容版本编译的。
干杯:如果你分发源代码并让客户端编译,那没问题
如果使用C++11,则可以使用cppcomponents。https://github.com/jbandela/cppcomponents
这将允许您在使用不同编译器或标准库创建的Dll/或.so文件中使用std::vector作为参数或返回值。看看我对类似问题的回答,例如通过dll边界传递对STL向量的引用
注意,对于该示例,您需要在CPPCOMPONENTS_DEFINE_FACTORY()
语句之后添加一个CPPCOMPONENTS_REGISTER(ImplementFiles)
- 从不同线程使用int64的不同字节安全吗
- 将数组作为参数传递给函数安全吗?作为第三方职能部门,可以探索他们想要的之外的其他元素
- 虚拟决赛作为安全
- 获取日期异步信号安全吗?如果在信号处理程序中使用,它会导致死锁吗
- 如何将元素添加到数组的线程安全函数?
- C++中的线程安全删除
- 通过网络、跨平台传递std::变体是否安全
- 在std::thread中,joinable()然后join()线程安全吗
- 使用std::istream::peek()总是安全的吗
- 从值小于256的uint16到uint8的Endian安全转换
- 在c++队列中使用pop和visit实现线程安全
- 在类型和包装器之间reinterpret_cast是否安全<Type>?
- 以线程安全的方式调用"QQuickPaintedItem::updateImage(const QImage&image)"(no QThread)
- 全局变量 多读取器 一个写入器多线程安全?
- 安全到标准:移动会员?
- AcquireCredentialsHandleA() 返回 PFX 文件的0x8009030e(安全包中没有可用的凭据
- 共享队列的线程安全
- boost::文件系统::recursive_directory_iterator多线程安全
- 跨 DLL 边界访问虚拟方法是否安全/可能?
- C++动态安全 2D 交错阵列