Если классы с виртуальными функциями реализованы с помощью vtables, как реализуется класс без виртуальных функций? - PullRequest
6 голосов
/ 19 сентября 2008

В частности, разве не должен быть какой-то указатель на функцию?

Ответы [ 9 ]

15 голосов
/ 19 сентября 2008

Я думаю, что фраза " классы с виртуальными функциями реализованы с помощью vtables " вводит вас в заблуждение.

Фраза звучит так, как будто классы с виртуальными функциями реализованы " способом A ", а классы без виртуальных функций реализованы " способом B ".

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

Подробнее о том, как они оба работают:

Все классы (с виртуальными или не виртуальными методами) являются структурами. Отличие * между структурой и классом в C ++ состоит в том, что по умолчанию члены являются открытыми в структурах и закрытыми в классах. Из-за этого я буду использовать термин «класс» для обозначения структур и классов. Помните, они почти синонимы!

члены данных

Классы - это (как и структуры) просто блоки непрерывной памяти, где каждый член хранится в последовательности. Обратите внимание, что иногда из-за архитектурных причин ЦП будут пропуски между элементами, поэтому блок может быть больше, чем сумма его частей.

Методы

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

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

void CMyThingy::DoSomething(int arg);
{
    // do something
}

Компилятор действительно делает это:

void CMyThingy_DoSomething(CMyThingy* this, int arg)
{
    /do something
}

Наконец, когда вы пишете это:

myObj.doSomething(aValue);

компилятор говорит:

CMyThingy_DoSomething(&myObj, aValue);

Нет необходимости в указателях функций где-либо! Компилятор уже знает, какой метод вы вызываете, поэтому он вызывает его напрямую.

Статические методы еще проще. У них нет указателя this , поэтому они реализованы именно так, как вы их пишете.

Это так! Остальное - просто удобное синтаксическое суммирование: компилятор знает, к какому классу принадлежит метод, поэтому он гарантирует, что он не позволит вам вызвать функцию, не указав, какой именно. Он также использует эти знания для перевода myItem в this->myItem, когда это однозначно.

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

( Редактировать : удалено последнее предложение и опубликовано отдельно, поэтому его можно критиковать отдельно)

12 голосов
/ 19 сентября 2008

Не виртуальные функции-члены на самом деле являются просто синтаксическим сахаром, поскольку они почти как обычная функция, но с проверкой доступа и неявным параметром объекта.

struct A 
{
  void foo ();
  void bar () const;
};

в основном совпадает с:

struct A 
{
};

void foo (A * this);
void bar (A const * this);

vtable необходим для того, чтобы мы вызывали правильную функцию для нашего конкретного экземпляра объекта. Например, если у нас есть:

struct A 
{
  virtual void foo ();
};

Реализация 'foo' может выглядеть примерно так:

void foo (A * this) {
  void (*realFoo)(A *) = lookupVtable (this->vtable, "foo");
  (realFoo)(this);   // Make the call to the most derived version of 'foo'
}
3 голосов
/ 19 сентября 2008

Виртуальные методы требуются, когда вы хотите использовать полиморфизм. Модификатор virtual помещает метод в VMT для позднего связывания, а затем во время выполнения решается, какой метод из какого класса будет выполнен.

Если метод не виртуальный - во время компиляции принимается решение, из какого экземпляра класса он будет выполняться.

Функциональные указатели используются в основном для обратных вызовов.

1 голос
/ 19 сентября 2008

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

Нет, нет указателей на функции; вместо этого компилятор переворачивает проблему наизнанку .

Компилятор вызывает глобальную функцию с указателем на объект вместо вызова некоторой указанной функции внутри объекта

Почему? Потому что обычно это намного эффективнее. Косвенные звонки - дорогие инструкции.

1 голос
/ 19 сентября 2008

Для невиртуального метода компилятор может генерировать нормальный вызов функции (например, CALL на конкретный адрес с этим указателем, переданным в качестве параметра) или даже встроить его. Для виртуальной функции компилятор обычно не знает во время компиляции, по какому адресу вызывать код, поэтому он генерирует код, который ищет адрес в vtable во время выполнения, а затем вызывает метод. Правда, даже для виртуальных функций компилятор иногда может правильно разрешить правильный код во время компиляции (например, методы для локальных переменных, вызываемые без указателя / ссылки).

1 голос
/ 19 сентября 2008

Если класс с виртуальной функцией реализован с помощью vtable, то класс без виртуальной функции реализуется без vtable.

Vtable содержит указатели на функции, необходимые для отправки вызова соответствующему методу. Если метод не виртуальный, вызов переходит к известному типу класса, и косвенное обращение не требуется.

0 голосов
/ 19 сентября 2008

Компилятор / компоновщик напрямую указывает, какие методы будут вызываться. Нет необходимости для vtable косвенности. Кстати, какое это имеет отношение к «стек против кучи»?

0 голосов
/ 19 сентября 2008

Ветви генерируются непосредственно в скомпилированном коде для методов; точно так же, как если у вас есть функции, которых вообще нет в классе, ветви генерируются прямо для них.

0 голосов
/ 19 сентября 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...