Как выглядят полиморфные объекты в памяти

Когда-то, я интересовался тем, как объекты С++ располагаются в памяти но быстрый гуглеж ничего не нашел. Недавно, я набрел на статью по теме и, думаю, она может помочь таким как я. Это — ее свободный перевод.

Полиморфизм

Способы представления абстрактных и не абстрактных объектов в памяти немного отличаются. Давайте, для примера, напишем несколько классов двумерных фигур: круг, прямоугольник и треугольник. Все они будут унаследованы от базового класса-фигуры «shape», который определяет общие для всех фигур свойства — положение, цвет границы и цвет заливки.

Каждый наследник добавляет дополнительные свойства для того, чтобы описать свою геометрию. К примеру, у круга это — радиус, а у треугольника — две стороны и угол между ними.

Код базового класса:

class shape {
public:
    shape();
    virtual double area() const;
    virtual double perimeter() const;
    ~~~
private:
    coordinates position;
    color outline, fill;
};


Если бы класс shape не содержал виртуальных методов, то память, отведенная под объекты этого типа содержала бы только данные:

Расположение в памяти объекта без виртуальных методов

В нашем-же примере есть виртуальные методы. Для реализации полиморфизма, компилятор C++ добавляет указатель на таблицу виртуальных функций, которую называют vtbl. Такой указатель называют vptr. Естественно, компилятор C++ создает vtbl и vptr только когда они нужны.

К примеру, класс «shape» содержит два виртуальных метода — area и perimeter. Соответственно, vtbl объектов этого класса содержит два указателя.

Распределение памяти абстрактного класса

Когда происходит наследование, данные наследника добавляются к базовому классу, а vtbl заменяется на таблицу методов, переопределенных наследником:

Для примера, рассмотрим такой вот класс:

class circle: public shape {
public:
    circle(double r);       // constructor
    virtual double area() const;
    virtual double perimeter() const;
    ~~~
private:
    double radius;
};


В памяти объект типа «circle» будет выглядеть вот так:

Расположение в памяти унаследованного полиморфного объекта.

Как видно, расположение данных базового класса внутри дочернего такое-же, как будто дочернего класса и нет вовсе — это нужно для того, чтобы с наследником можно было обращаться как с базовым классом.

Вот так, никакой магии и довольно все просто под капотом у C++.

Небольшое дополнение: Интересно стало — сколько команд занимает вызвать виртуальный метод. Вот что получилось:
Сколько команд занимает вызов виртуального метода

Не так уж и страшно, можно совершенно свободно пользоваться.

6 комментариев

avatar
А это для какого компилятора? Например, между майкрософтовским и GCC есть отличия, которые мешают использовать одинаковые библиотеки на C++.
avatar
Это — для всех компиляторов, просто потому, что по другому не сделаешь.
avatar
Виртуальный метод не может быть статичным.
На микроконтроллере создание объекта (если еще тем более в куче) может ухудшить скорость работы кода.
Как правильно, если использую C++ на мк, стараюсь обходится static-only классами.
avatar
Дак, если в классе только статичные методы, то в нем (классе) смысла нет. Тогда уж лучше не классы а namespace использовать. А виртуальность и куча — совершенно разные понятия. Вот более микроконтроллерный пример:


class ComPortChannel : public ICommunicationChannel;
class UsbChannel : public ICommunicationChannel;


ComPortChannel com_port;
UsbPort usb_port;
...

data.send_via(&com_port);
data.send_via(&usb_port);
avatar
Если объект не имеет статичных полей, чтобы поюзать его, его нужно создать:) а создать как? на стеке или в куче? а если в объекте массив байт на 1000[]? Порвет стек?
Имеет смысл использовать статичный класс, чтобы юзать модификаторы доступа к полям и методам. А как Вы создаете объекты в микроконтроллерных приложениях, статически или динамически? (имеется в виду в более серьезных uc, типо ARM).
avatar
Никто не мешает создавать объекты с виртуальными функциями статически.

Если выбор стоит только между стеком и кучей (static-only классы, кстати нельзя создать на стеке или в куче — вот и потеря гибкости), то стек не порвет — просто стек нужно больше сделать и все. По использованию памяти будет — абсолютно тоже самое, что и выделить память для статического объекта. Кроме того, стек можно еще и в других местах использовать.

Лично я объекты создаю только статически. Иногда использую allocate-only кучу.
Комментарий отредактирован 2013-01-16 14:42:37 пользователем bsvi
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.