Как вы делаете наследование на не-OO языке? - PullRequest
2 голосов
/ 29 октября 2009

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

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

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

Ответы [ 7 ]

5 голосов
/ 29 октября 2009

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

struct Base {
    int blah;
};

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

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

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

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

5 голосов
/ 29 октября 2009

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

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

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

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

Первое издание Miro Samek «Практические диаграммы состояний в 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
2 голосов
/ 29 октября 2009

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

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 ++, превратился в плановый вызов C.

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

1 голос
/ 29 октября 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 ++).

1 голос
/ 29 октября 2009

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

0 голосов
/ 29 октября 2009

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

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

0 голосов
/ 29 октября 2009

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

...