为您的 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 并不重要。两者在类型定义之前是可以互换的。