指向 C++ class 的不透明 C 指针的正确类型定义是什么?
What is the correct typedef for an opaque C pointer to a C++ class?
有数十个 SO 问题和博客文章描述了用 C API 包装 C++ class。示例 Wrapping C++ class API for C consumption
这些答案和博文中的大多数都是这样的:
typedef void* CMyClass;
但是其他人说这很糟糕,因为它没有提供类型安全。他们提出了不透明结构的各种变体,但没有任何解释。我可以复制上面的片段并继续我的生活(同时我会这样做),但我想一劳永逸地知道
- 哪种形式最好?
- 它比
void*
提供了哪些保证?
- 它是如何工作的?
void *
的问题在于它无法防止意外分配不兼容的指针。
typedef void *CMyClass;
int i = 1;
CMyClass c = &i; // No complaints
如果您将 typedef 改为某种独特的不透明类型,编译器会帮助您。
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 *'
在 C++ 中使用 struct MyType
。
使用typedef struct MyType* pMyType;
作为您的常用句柄。
您的 "C" API 应该在 C 和 C++ 中编译(使用 C++ 中的 extern "C"
包装器以获得正确的链接)。而且您将接近最大类型安全性。
现在,struct MyHandle{void* private_ptr;};
是另一种选择:这避免了将 C++ 类型的名称暴露给 C。只要将与 private_ptr
的直接交互隔离到少数函数,它就会在其他任何地方都一样安全。
首先,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};
// ...
}
}
有数十个 SO 问题和博客文章描述了用 C API 包装 C++ class。示例 Wrapping C++ class API for C consumption
这些答案和博文中的大多数都是这样的:
typedef void* CMyClass;
但是其他人说这很糟糕,因为它没有提供类型安全。他们提出了不透明结构的各种变体,但没有任何解释。我可以复制上面的片段并继续我的生活(同时我会这样做),但我想一劳永逸地知道
- 哪种形式最好?
- 它比
void*
提供了哪些保证? - 它是如何工作的?
void *
的问题在于它无法防止意外分配不兼容的指针。
typedef void *CMyClass;
int i = 1;
CMyClass c = &i; // No complaints
如果您将 typedef 改为某种独特的不透明类型,编译器会帮助您。
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 *'
在 C++ 中使用 struct MyType
。
使用typedef struct MyType* pMyType;
作为您的常用句柄。
您的 "C" API 应该在 C 和 C++ 中编译(使用 C++ 中的 extern "C"
包装器以获得正确的链接)。而且您将接近最大类型安全性。
现在,struct MyHandle{void* private_ptr;};
是另一种选择:这避免了将 C++ 类型的名称暴露给 C。只要将与 private_ptr
的直接交互隔离到少数函数,它就会在其他任何地方都一样安全。
首先,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};
// ...
}
}