Использование структуры C++ с виртуальными элементами (т.е. не-POD) в C

В таких вопросах, как это, совместимость между классами/структурами C++ и C-структуры объясняются настолько, насколько это возможно, если все элементы имеют один и тот же тип, в одном и том же порядке и не объявлены виртуальные члены.

Это моя проблема. У меня есть виртуальные методы, и я бы очень хотел сохранить их при работе со структурой в C++.

Давайте рассмотрим этот игрушечный пример. Это должна быть совместимая с C и C++ структура, определенная в одном заголовочном файле.

моястр.ч:

#ifdef __cplusplus
#include <string>
struct mystr_base {
    virtual ~mystr_base() {}

    virtual std::string toString() = 0;
};
#endif

#ifdef __cplusplus
extern "C" {
#endif

struct mystr
#ifdef __cplusplus
: public mystr_base
#endif
{
    const char* data;

#ifdef __cplusplus
    std::string toString() {
        return std::string(data);
    }
#endif
};

#ifdef __cplusplus
}
#endif

Это может быть не совсем красиво, но для примера сойдет. В реальном сценарии варианты C и C++ могут находиться в отдельных заголовках, а структура C++ расширяет структуру POD. Независимо от реализации, проблема выравнивания все еще присутствует.

В этом примере, если программа C была написана, которая передает экземпляр mystr функции C++, виртуальная таблица будет мешать выравниванию:

тест.ч:

#include "mystr.h"

#ifdef __cplusplus
extern "C"
#endif
void mycxxfunc(struct mystr str);

test.cpp:

#include <stdio.h>
#include "test.h"

void mycxxfunc(mystr str) {
    printf("mystr: %s\n", str.data);
}

основной.с:

#include "test.h"

int main(int argc, char** argv) {
    const char* testString = "abc123";
    struct mystr str;
    str.data = testString;
    mycxxfunc(str);
}

$ g++ -c test.cpp && gcc main.c test.o
$ ./a.out
Segmentation fault (core dumped)

(предполагая, что это связано с тем, что функция С++ пытается прочитать data из-за конца выделенной памяти структуры)

Каков наилучший способ включить эту совместимость C-C++, сохраняя при этом возможность использования виртуальных функций в C++?


person Chris Watts    schedule 15.11.2017    source источник
comment
Структуры не имеют функций-членов в c.   -  person user0042    schedule 15.11.2017
comment
@user0042 user0042 При компиляции на C функции-члены не определены. Вполне допустимо иметь структуру POD, которая включает в себя функции-члены в C++, которые будут иметь точно такое же расположение памяти при использовании в C.   -  person Chris Watts    schedule 15.11.2017
comment
Я бы посоветовал попробовать смешать C и C++ таким образом. Держите их отдельно. Иметь класс C++ с переменной-членом структуры POD. Если вам нужно, передайте токен (void*) на сторону C API для экземпляра класса C++ и упакуйте его (в void* или обратно в указатель класса C++) на этом барьере API C/C++. .   -  person Eljay    schedule 15.11.2017
comment
В общем, полиморфные классы нельзя сделать совместимыми с C-эквивалентными, потому что C не имеет эквивалента. Вам нужно знать, как эти классы скомпилированы. Тогда ваш код будет работать только для ABI компилятора.   -  person curiousguy    schedule 23.11.2017


Ответы (1)


Я не рекомендую вам загромождать ваш заголовочный файл #ifdefs.

Первое, что вы должны сделать в этом случае, если хотите сохранить какую-то виртуализацию и совместимость с C одновременно, это:

  1. Сделайте ваш C++ и C тип непрозрачным указателем на представление.
  2. Поместите детали реализации в файл .cpp.

Далее следует идея.

Заголовочный файл:

struct MyStrImpl;

struct MyStr {
   MyStrImpl * impl;
};

extern "C" MyReturnType myFunction(MyStr myStr);

Реализация в .cpp файле:

struct MyCppString {
    virtual ...
};

#ifdef __cplusplus
struct MyStrImpl : public MyCppString {

};
#else
struct MyStrImpl {

};
#endif
MyStr::MyStr() : impl{new MyStrImpl{}} {

}

Таким образом, у вас есть тип, который можно использовать как в C, так и в C++.

Преимущества:

  • Нет #ifdef в заголовочном файле.
  • Совместимость с C/C++ и совместимость с обоими языками.

Недостатки:

  • Потеря перегрузки из-за внешнего "C".
  • Должен использовать интерфейс в стиле C (но может быть реализован в стиле C++ с интерфейсом виртуальных функций в вашем файле .cpp.

Вы не можете одновременно иметь C-совместимый тип и виртуальные функции в заголовочном файле, не загромождая его #ifdef, чего я не рекомендую, поскольку это может быть неудобно, если вам нужно рефакторить код.

person Germán Diago    schedule 15.11.2017
comment
Я не поклонник этого ответа, так как он делает использование неуклюжим. Например, инициализация в C будет выглядеть как MyStrImpl* impl = (MyStrImpl*) malloc(sizeof(MyStrImpl)); MyStr str; str.impl = impl;, а использование методов в C++ будет иметь вид str.impl->toString(). Это может быть хорошо для внутреннего кода, но неприемлемо для API. - person Chris Watts; 15.11.2017
comment
В любом случае, если вам нужно что-то более причудливое, решение сводится к дублированию реализации C и C++, если вам нужны разные интерфейсы. Вы можете добавить функции C для построения/уничтожения с текущим решением. - person Germán Diago; 15.11.2017
comment
Конечно, я мог бы создать функцию-конструктор на C. Это должно было бы предполагать, что вызывающая сторона не против использования malloc (что может быть не всегда так). Это больше касается простоты использования. Пользователь структуры, будь то на C или C++, никогда не должен беспокоиться о деталях реализации и просто писать код семантически. Мне интересно, будет ли пользовательская упаковка делать то, что я хочу. - person Chris Watts; 15.11.2017
comment
Тот факт, что вы прячете конструктор за интерфейсом C, не означает, что будет использоваться malloc. Я понимаю вашу идею о простоте использования, но это сильно усложнит реализацию. Я бы порекомендовал в этом случае разделить реализацию C и C++, иначе у вас будет возможность загромождать вещи с помощью #ifdefs. Вы не можете гарантировать макет без косвенного AFAIK. - person Germán Diago; 15.11.2017
comment
Я лично предпочитаю трудный способ делать вещи. Если я смогу сделать его простым в использовании, с надлежащей абстракцией, я это сделаю. В любом случае я могу избежать большого количества #ifdefs, разделив заголовки C и C++. - person Chris Watts; 15.11.2017