对于指向C++类的不透明C指针,正确的typedef是什么



有几十个SO问题和博客文章描述了用C API包装C++类。用于C消耗的包装C++类API的示例

这些答案和博客文章中的大多数都是这样的:

typedef void* CMyClass;

但也有人说这很糟糕,因为它没有提供任何类型的安全性。他们提出了不透明结构的各种变体,没有任何解释。我可以复制上面的片段,继续我的生活(在此期间我会这样做(,但我想一劳永逸地知道

  • 哪种形式最好
  • 它对void*提供哪些保证
  • 它是如何工作的

在C++中使用struct MyType

使用typedef struct MyType* pMyType;作为常用句柄。

您的"C"API应该同时在C和C++中编译(在C++中使用extern "C"包装器以获得正确的链接(。您将接近最大类型的安全性。

现在,struct MyHandle{void* private_ptr;};是另一个选项:这避免了将C++类型的名称暴露给C。只要将与private_ptr的直接交互隔离为少数函数,它在其他任何地方都是类型安全的。

void *的问题在于,它无法防止意外分配不兼容的指针。

typedef void *CMyClass;
int i = 1;
CMyClass c = &i; // No complaints

如果您将def改为某个唯一的不透明类型,编译器将帮助您。

typedef struct MyClass *CMyClass;
int i = 1;
CMyClass c = &i; // BOOM!

我认为在C中这不是一个错误,但Clang 6.0警告我使用(即使没有启用任何警告(

warning: incompatible pointer types initializing 'CMyClass' (aka 'struct MyClass *') with an expression of type 'int *'

首先,void *确实不是一个好选择,因为它通过静默地接受任何不相关的指针,使API更容易出错。因此,更好的想法是向某个结构添加一个正向声明,并接受指向该结构的指针:

#ifdef __cplusplus
extern "C"
{
#endif
struct CMyClassTag;
typedef struct CMyClassTag CMyClass;
void CMyClass_Work(CMyClass * p_self);
#ifdef __cplusplus
}
#endif

下一步是明确地告诉用户,这个指针是不透明的,不应该通过隐藏指针作为不必要的实现细节来取消引用:

typedef struct CMyClassTag * CMyClassHandle;
void CMyClass_Work(CMyClassHandle h_my_class);

此外,您可以使真正的句柄类型而不是不透明的指针,而不是依靠用户正确地使用此接口。这可以通过几种方式实现,但主要思想是传递一些模糊的整数标识符,并在运行时执行从它到库端实际指针的映射:

typedef uintptr_t CMyClassHandle;
void CMyClass_Work(CMyClassHandle h_my_class);
// impl
void CMyClass_Work(CMyClassHandle h_my_class)
{
auto it{s_instances_map.find(h_my_class)};
if(s_instances_map.end() != it)
{
auto & self{it->second};
// ...
}
}

最新更新