Предоставление C API для вашей библиотеки C++ и строгого сглаживания

Обычным шаблоном при предоставлении API-интерфейса C является пересылка объявления некоторых непрозрачных типов в ваш общедоступный заголовок, которые передаются вашим API-методам, а затем reinterpret_cast их в определенные типы C++ один раз внутри единицы перевода (и, следовательно, обратно в C++ земельные участки).

Использование LLVM в качестве примера:

В Types.h это typedef объявлено:

typedef struct LLVMOpaqueContext *LLVMContextRef;

LLVMOpaqueContext не упоминается нигде в проекте.

В Core.h объявлен следующий метод:

LLVMContextRef LLVMContextCreate(void);

Что определено в Core.cpp:

LLVMContextRef LLVMContextCreate() {
  return wrap(new LLVMContext());
}

wrapunwrap) определяется макросом в 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 и LLVMOpaqueContext его в объект llvm::LLVMContext для выполнения любого метода, вызываемого на нем.

Мой вопрос: разве это не нарушает строгие правила псевдонимов? Если нет, почему бы и нет? И если да, то как этот тип абстракции на границе публичного интерфейса должен быть законным?

Ответ 1

Это не строгое нарушение псевдонимов. Начнем с того, что строгое сглаживание связано с доступом к объекту с помощью значения glvalue неправильного типа.

В вашем вопросе вы создаете LLVMContext, а затем используете LLVMContext lvalue для доступа к нему. Никакого незаконного наложения не существует.

Единственная проблема, которая может возникнуть, заключается в том, что преобразование указателя не возвращает один и тот же указатель. Но это тоже не проблема, поскольку reinterpret_cast гарантированно возвращает один и тот же указатель в конверсии с обратным проходом. До тех пор, пока тип указателя, который мы преобразуем в и обратно, соответствует соответствующим образом выровненным данным (то есть не более строгим, чем исходный тип).

Является ли это хорошим или плохим способом заниматься вещами, является спорным. Я лично не буду беспокоиться о LLVMOpaqueContext и возвращать struct LLVMContext*. Это все еще непрозрачный указатель, и не имеет значения, что заголовок C объявляет его со struct а определение типа - с class. Эти два взаимозаменяемы до точки определения типа.