Как сделать наследование на языке, отличном от объектно-ориентированного?

Я читал, что ранние «компиляторы» C ++ фактически переводили код C ++ на C и использовали компилятор C на бэкэнде, и это заставило меня задуматься. У меня достаточно технических знаний, чтобы осмыслить большую часть того, как это будет работать, но я не могу понять, как сделать наследование классов, не имея для этого языковой поддержки.

В частности, как вы определяете класс с несколькими полями, а затем с набором подклассов, которые наследуются от него, и каждый из них добавляет свои собственные новые поля и может передавать их взаимозаменяемо в качестве аргументов функции? И особенно, как вы можете это сделать, когда C ++ позволяет размещать объекты в стеке, поэтому у вас может даже не быть указателей, за которыми можно спрятаться?

ПРИМЕЧАНИЕ. Первые пару ответов, которые я получил, касались полиморфизма. Я знаю все о полиморфизме и виртуальных методах. Я даже однажды провел презентацию на конференции о низкоуровневых деталях работы таблицы виртуальных методов в Delphi. Меня интересует наследование классов и поля, а не полиморфизм.


person Mason Wheeler    schedule 29.10.2009    source источник
comment
(Почти) точный дубликат: stackoverflow.com/questions/415452/object- ориентация-в-с stackoverflow.com/questions/351733/ stackoverflow.com/questions/1201521/ и, возможно, больше   -  person Adam Rosenfield    schedule 29.10.2009
comment
Тогда я немного запутался ... с полями легко (по крайней мере, для C) - просто включите базовую структуру в качестве первого элемента в производную структуру. Или я упускаю еще один нюанс в вопросе?   -  person Michael Burr    schedule 29.10.2009
comment
Интересно отметить, что людям, как правило, трудно понять, как (или иногда даже это) наследование и другие функции объектно-ориентированного программирования могут быть реализованы в C, когда C не поддерживает их, при этом совершенно не задаваясь вопросом, как те же функции могут быть реализованы в C. ассемблерный или машинный код, где у вас даже нет структур.   -  person Mikael Auno    schedule 29.10.2009
comment
Это не так уж и сложно. Я знаю, как работают структуры на уровне сборки. :П   -  person Mason Wheeler    schedule 29.10.2009
comment
Как бы то ни было, разве Comeau C ++ не компилируется до C? Вы, наверное, могли бы увидеть, как это делается во всех подробностях.   -  person Chris Lutz    schedule 29.10.2009
comment
С другой стороны, прямо до вашего вопроса это казалось мне довольно очевидным, поэтому теперь у меня есть мучительные сомнения, что я, должно быть, что-то пропустил ... Если вы думаете о Python, 'this' - просто неявный аргумент в пользу нестатичности методы класса, различные атрибуты просто позиционируются с точным смещением от this (в зависимости от типа, но известны во время компиляции), а методы - это просто функции, которые вы вызываете через указатель на функцию ...   -  person Matthieu M.    schedule 29.10.2009


Ответы (7)


В любом случае в C вы делаете это так, как это делал cfront в первые дни C ++, когда код C ++ был переведен на C. Но вам нужно быть достаточно дисциплинированным и выполнять всю работу вручную.

Ваши «классы» должны быть инициализированы с помощью функции, выполняющей работу конструктора. это будет включать инициализацию указателя на таблицу указателей полиморфных функций для виртуальных функций. Вызов виртуальных функций должен выполняться через указатель функции vtbl (который будет указывать на структуру указателей функций - по одному для каждой виртуальной функции).

Структура виртуальной функции для каждого производного класса calss должна быть надмножеством структуры базового класса.

Некоторые механизмы этого можно скрыть / помочь с помощью макросов.

Первое издание "Практические диаграммы состояний на C / C ++" Миро Самека содержит приложение A - «C + - объектно-ориентированное программирование на C», в котором есть такие макросы. Похоже, это было исключено из второго издания. Наверное, потому, что это больше хлопот, чем того стоит. Просто используйте C ++, если хотите ...

Вам также следует прочитать Липпмана "Внутри объекта C ++ Модель ", в которой подробно рассказывается о том, как C ++ работает" за кулисами ", часто с фрагментами того, как что-то может работать в C.


Думаю, я понимаю, что тебе нужно. Может быть.

Как может что-то вроде этого работать:

typedef 
struct foo {
    int a;
} foo;

void doSomething( foo f);   // note: f is passed by value

typedef
struct bar {
    foo base;
    int b;
} bar;


int main() {
    bar b = { { 1 }, 2};

    doSomething( b);    // how can the compiler know to 'slice' b
                        //  down to a foo?

    return 0;
}

Что ж, вы не можете сделать это так просто без языковой поддержки - вам нужно будет сделать некоторые вещи вручную (вот что означает отсутствие языковой поддержки):

    doSomething( b.base);       // this works
person Michael Burr    schedule 29.10.2009
comment
Хороший ответ, но я спрашивал не об этом. - person Mason Wheeler; 29.10.2009

По сути, структуры внутри структур.

struct Base {
    int blah;
};

struct Derived {
    struct Base __base;
    int foo;
};

Когда вы хотите, скажем, преобразовать Derived * в Base *, вы фактически вернете указатель на элемент __base производной структуры, которая в этом случае является первой вещью в структуре, поэтому указатели должны быть одинаковыми (не было бы ' Однако это относится к классам с множественным наследованием).

Если вы хотите получить доступ к blah в этом случае, вы должны сделать что-то вроде derived.__base.blah.

Виртуальные функции обычно выполняются с помощью специальной таблицы указателей функций, которая является частью каждого объекта, элементарной записи типа «какой у меня тип».

person Artelius    schedule 29.10.2009
comment
Ага! В этом есть смысл. И это также объясняет некоторые уродливые стороны C ++ при работе с иерархией наследования. - person Mason Wheeler; 29.10.2009

Вот как это делает COM для языка C. Я немного подзабыл, но суть такова. Каждая переменная-член «класса» - это просто структура.

struct Shape
{
  int value;
};

struct Square
{
  struct Shape shape; // make sure this is on top, if not KABOOM
  int someothervalue;
};

все методы на самом деле являются обычными функциями. нравится

void Draw(Shape * shape,int x,int y)
{
  shape->value=10; // this should work even if you put in a square. i think...
}

затем они используют препроцессор, чтобы «обмануть» код C, чтобы он отобразил что-то вроде этого.

Square * square;
square->Draw(0,0); // this doesnt make sense, the preprocessor changes it to Draw(square,0,0);

Увы, я не знаю, какие уловки препроцессора используются, чтобы преобразовать вызов функции, похожий на C ++, в вызов Plan vanilla C.

Таким образом объявляются COM-объекты DirectX.

person Andrew Keith    schedule 29.10.2009

У доктора Добба была умеренно подробная статья на эту тему: Классы одиночного наследования в C.

person Boojum    schedule 29.10.2009

Структуры внутри структур - это обычное дело, но это затрудняет доступ к унаследованным полям. Вам нужно либо использовать косвенное обращение (например, child->parent.field), либо преобразование (((PARENT *) child)->field).

Альтернатива, которую я видел, больше похожа на это:

#define COUNTRY_FIELDS \
    char *name; \
    int population;

typedef struct COUNTRY
{
    COUNTRY_FIELDS
} COUNTRY;

#define PRINCIPALITY_FIELDS \
    COUNTRY_FIELDS \
    char *prince;

typedef struct PRINCIPALITY
{
    PRINCIPALITY_FIELDS
} PRINCIPALITY;

Это дает типам прямой доступ к унаследованным полям. Полученные объекты по-прежнему можно безопасно привести к родительскому типу, потому что поля родительского и унаследованные поля начинаются в одном месте.

Синтаксис можно немного улучшить с помощью макросов. Я видел это в более старом исходном коде POV-Ray (но я думаю, что с тех пор они преобразованы в C ++).

person Edmund    schedule 29.10.2009

Если вам нужна хорошая справка о том, как это работает, взгляните на библиотеки с открытым исходным кодом glib / gdk / gtk. У них довольно хорошая документация, и весь фреймворк основан на C OO.

person ssteidl    schedule 29.10.2009

Вы можете имитировать объект, написав конструкторы, сеттеры, геттеры и деструкторы со скрытым указателем this, вызываемым явно.

Наследование осуществляется путем включения в производный объект указателя на базовый объект в структуре производного объекта.

person David Harris    schedule 29.10.2009