为您的 C++ 库提供 C API 和严格的别名
Providing a C API to your C++ library and strict aliasing
提供 C API 时的一个常见模式是在您的 public header 中转发声明一些不透明类型,这些类型将传递给您的 API 方法,然后 reinterpret_cast
一旦进入翻译单元(并因此回到 C++ 领域),它们就会变成您定义的 C++ 类型。
以LLVM为例:
在 Types.h 中声明了这个类型定义:
typedef struct LLVMOpaqueContext *LLVMContextRef;
LLVMOpaqueContext
未在项目的其他任何地方引用。
在 Core.h 中声明了以下方法:
LLVMContextRef LLVMContextCreate(void);
定义在Core.cpp:
LLVMContextRef LLVMContextCreate() {
return wrap(new LLVMContext());
}
wrap
(和unwrap
)由CBindingWrapping.h中的宏定义:
#define DEFINE_SIMPLE_CONVERSION_FUNCTIONS(ty, ref) \
inline ty *unwrap(ref P) { \
return reinterpret_cast<ty*>(P); \
} \
\
inline ref wrap(const ty *P) { \
return reinterpret_cast<ref>(const_cast<ty*>(P)); \
}
并用于LLVMContext.h:
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(LLVMContext, LLVMContextRef)
所以我们看到 C API 基本上采用指向 LLVMOpaqueContext
的指针并将其转换为 llvm::LLVMContext
object 以执行对其调用的任何方法.
我的问题是:这不是违反了严格的别名规则吗?如果不是,为什么不呢?如果是这样,如何在 public 接口边界上合法地实现这种类型的抽象?
这不是严格的别名违规。首先,严格别名是关于通过错误类型的泛左值访问对象。
在您的问题中,您创建了一个 LLVMContext
,然后使用 LLVMContext
左值来访问它。那里没有非法别名。
唯一可能出现的问题是指针转换是否返回相同的指针。但这也不是问题,因为 reinterpret_cast
保证在 round-trip 转换中返回相同的指针。只要我们转换为和返回的指针类型是适当对齐的数据(即不比原始类型更严格)。
这种处理方式是好是坏还有待商榷。我个人不会理会 LLVMOpaqueContext
和 return 和 struct LLVMContext*
。它仍然是一个不透明的指针,C 头文件使用 struct
声明它而类型定义使用 class
并不重要。两者在类型定义之前是可以互换的。
提供 C API 时的一个常见模式是在您的 public header 中转发声明一些不透明类型,这些类型将传递给您的 API 方法,然后 reinterpret_cast
一旦进入翻译单元(并因此回到 C++ 领域),它们就会变成您定义的 C++ 类型。
以LLVM为例:
在 Types.h 中声明了这个类型定义:
typedef struct LLVMOpaqueContext *LLVMContextRef;
LLVMOpaqueContext
未在项目的其他任何地方引用。
在 Core.h 中声明了以下方法:
LLVMContextRef LLVMContextCreate(void);
定义在Core.cpp:
LLVMContextRef LLVMContextCreate() {
return wrap(new LLVMContext());
}
wrap
(和unwrap
)由CBindingWrapping.h中的宏定义:
#define DEFINE_SIMPLE_CONVERSION_FUNCTIONS(ty, ref) \
inline ty *unwrap(ref P) { \
return reinterpret_cast<ty*>(P); \
} \
\
inline ref wrap(const ty *P) { \
return reinterpret_cast<ref>(const_cast<ty*>(P)); \
}
并用于LLVMContext.h:
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(LLVMContext, LLVMContextRef)
所以我们看到 C API 基本上采用指向 LLVMOpaqueContext
的指针并将其转换为 llvm::LLVMContext
object 以执行对其调用的任何方法.
我的问题是:这不是违反了严格的别名规则吗?如果不是,为什么不呢?如果是这样,如何在 public 接口边界上合法地实现这种类型的抽象?
这不是严格的别名违规。首先,严格别名是关于通过错误类型的泛左值访问对象。
在您的问题中,您创建了一个 LLVMContext
,然后使用 LLVMContext
左值来访问它。那里没有非法别名。
唯一可能出现的问题是指针转换是否返回相同的指针。但这也不是问题,因为 reinterpret_cast
保证在 round-trip 转换中返回相同的指针。只要我们转换为和返回的指针类型是适当对齐的数据(即不比原始类型更严格)。
这种处理方式是好是坏还有待商榷。我个人不会理会 LLVMOpaqueContext
和 return 和 struct LLVMContext*
。它仍然是一个不透明的指针,C 头文件使用 struct
声明它而类型定义使用 class
并不重要。两者在类型定义之前是可以互换的。