Вызов виртуальных методов в конструкторах

Модераторы: Hawk, Romeo, Absurd, DeeJayC, WinMain

Аватара пользователя
Airhand
Сообщения: 239
Зарегистрирован: 06 окт 2005, 16:21
Откуда: Dnepropetrovsk

BBB
Противоречие НЕ в том, в какой последовательности вызываются конструкторы. Я писал о том, что таблица виртуальных функций инициализируется в последнем действии конструктора, а не до него.
Оптимизация по скорости:
#define while if
Оптимизация по размеру:
#define struct union
BBB
Сообщения: 1298
Зарегистрирован: 27 дек 2005, 13:37

Airhand писал(а): Я писал о том, что таблица виртуальных функций инициализируется в последнем действии конструктора, а не до него.
Совершенно неочевидно. Я согласен, что при входе в тело констркутора vptr уже проинициализирован.
Аватара пользователя
Romeo
Сообщения: 3126
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

Airhand, я не отвечал долго потому, что решил набросать программку, которая подтвердит мои слова, а также докажет, что не каждой книге можно доверять. Есть признанные авторы, которых стоит читать, например Страуструп. Автор же упомянутой статьи писал о вещах, в которых толком не разбирается. Airhand, не верь всему, что написано в интернете. Интернет большой, а авторов очень много и не все они глаголят истину. Ты же не веришь всему, что написано на заборе возле твоего дома? ;) И здесь тоже доверяй, но проверяй.

Короче вот программа:

Код: Выделить всё

#include <iostream>

static const void* g_pBaseTableHead = NULL;
static const void* g_pDerivedTableHead = NULL;

const void* GetVPtr(const void* pObject)
{
   const long nTableAddr = *((const long*)pObject);
   const void* pTableHead = (const void*)nTableAddr;
   return pTableHead;
}

const void* PrintObjectInfoAndGetTable(const char* pszClassName, const void* pObject)
{
   std::cout << "Information for object of class '" << pszClassName << "'" << std::endl;
   std::cout << "Object address: " << pObject << std::endl;

   const void* pVPtr = GetVPtr(pObject);
   std::cout << "Address of virtual table: " << (const void*)pVPtr << std::endl << std::endl;

   return pVPtr;
}

void DumpVTable(const void* pVPtr)
{
   std::cout << "Dumping virtual table by double words. Table address: " << pVPtr << std::endl;

   const long* pDWordsRepresentation = (const long*)pVPtr;
   for (int i = 0; i < 5; i++)
   {
      std::cout << std::hex << "0x" << pDWordsRepresentation[i] << " ";
   }

   std::cout << std::endl << std::endl;
}

class CBase
{
public:
   CBase()
   {
      g_pBaseTableHead = PrintObjectInfoAndGetTable("CBase", this);
      DumpVTable(g_pBaseTableHead);
   }

   virtual void V1() = 0;
   virtual void V2() = 0;
};

class CDerived : public CBase
{
public:
   CDerived()
   {
      g_pDerivedTableHead = PrintObjectInfoAndGetTable("CDerived", this);
      DumpVTable(g_pBaseTableHead);
      DumpVTable(g_pDerivedTableHead);
   }

   virtual void V1() {}
   virtual void V2() {}
   virtual void V3() {}
};


int main()
{
   CDerived derived;
   return 0;
}
Вот output программы у меня:

Код: Выделить всё

Information for object of class 'CBase'
Object address: 0012FF7C
Address of virtual table: 0046F0EC

Dumping virtual table by double words. Table address: 0046F0EC
0x407530 0x407530 0x0 0x7373696d 0x20676e69

Information for object of class 'CDerived'
Object address: 0012FF7C
Address of virtual table: 0046F0D4

Dumping virtual table by double words. Table address: 0046F0EC
0x407530 0x407530 0x0 0x7373696d 0x20676e69

Dumping virtual table by double words. Table address: 0046F0D4
0x4010eb 0x40118b 0x4011bd 0x0 0x73614243
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Аватара пользователя
Romeo
Сообщения: 3126
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

Естествено у вас будут другие цифры, но нам нужны не сами цифры, а закономерности. Давайте разберём код.

И так, в конструкторе базового и производного класса выводится значение указателя на виртуальные таблицу. Из output'а видно, что указатель имеет корректное значение уже в конструкторе (а не устанавливается после завершения конструктора), а также видно, что значение указателя разное в конструкторе базового и наследника. То есть всё работает согласно схеме, которую я вывел путём умозаключений. Теперь это подтверждено фактами.

Теперь что касается строчек "Dumping virtual table". Дампинг производится три раза. Два раза для таблицы базового класса (из конструктора базового и из конструктора наследника) и один раз для таблицы наследника (из конструктора наследника). Первые два выводы признаны показать, что виртуальные таблицы НЕ МЕНЯЮТСЯ в runtime! Прошу читателей внимательно посмотреть на output, и убедится, что содержимое таблицы базового класса (0046F0EC) идентично в первом и во втором выводе. Таблица наследника выведена для наглядности, а также для того, чтобы я мог немного рассказать вообще о структуре виртуальных таблиц.

Первое, на что хочу обратить внимание - это некий коварный адрес 0x407530, который фигурирует в двух первых ячейках таблицы базового класса (таблица с адресом 0046F0EC). Если посмотреть на код, то мы увидим, что методы, соответствующие этим адресам являются pure virtual. Вначале мы удивимся и скажем: "Так ведь в таком случае в таблице должны быть нули!". Однако это вывод поспешен. Если немного подумать, то мы поймём, что операционная система не может различить когда у нас произошёл pure virtual вызов и если бы там были нули, но наше приложение бы корилось (напомню, что никаких проверок перед вызовом не делается). Таким образом мы осознаём, что этот магический адрес - это не что иное, как адрес функции, которая показывает сообщение об ошибке, а потом завершает выполнение программы. То есть и программист доволен, что он знает где исправлять, и программа не скорилась, а завершила работу корректно. Отсюда мы почерпнули важные знания: об ошибке "pure virtual call" нам сообщает не система, а сама наша программа, благодаря грамотно написанному компилятору.

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

Перейдём к дампингу таблицы класса-наследника (таблица с адресом 0046F0D4). Здесь мы видим, что первые две ячейки таблицы, заполнены уже не адресом обработчика ошибок, а двумя честными адресами методов. Честным адресом метода также является и третья ячейка таблицы (обратите внимание по коду, что класс-наследник имеет дополнительный метод V3). Четвёртая ячейка хранит терминирующий ноль, что мы и ожидали увидеть.

Надеюсь, пример достаточно нагляден, чтобы решить все споры? :)
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Аватара пользователя
Airhand
Сообщения: 239
Зарегистрирован: 06 окт 2005, 16:21
Откуда: Dnepropetrovsk

Romeo писал(а):Airhand, я не отвечал долго потому, что решил набросать программку, которая подтвердит мои слова, а также докажет, что не каждой книге можно доверять. Есть признанные авторы, которых стоит читать, например Страуструп.
Так покажи мне ссылку на его слова. Я не разбирался в твоей проге и в её выводе.
Автор же упомянутой статьи писал о вещах, в которых толком не разбирается. Airhand, не верь всему, что написано в интернете. Интернет большой, а авторов очень много и не все они глаголят истину.
Тот автор, как я позже узнал, Ален Голуб.
Ты же не веришь всему, что написано на заборе возле твоего дома? ;) И здесь тоже доверяй, но проверяй.
Я бы так не говорил про него. То, что ты утверждаеш тоже в инете находится. А проверить некак.
--------------------------------------------------------------------------------
Добавлено сообщение
--------------------------------------------------------------------------------
Romeo писал(а):И так, в конструкторе базового и производного класса выводится значение указателя на виртуальные таблицу. Из output'а видно, что указатель имеет корректное значение уже в конструкторе (а не устанавливается после завершения конструктора), а также видно значение указателя разное в конструкторе базового и наследника. То есть всё работает согласно схеме, которую я вывел путём умозаключений. Теперь это подтверждено фактами.
Кто тебе сказал, что то, что ты печатаешь - таблица виртуальных функций. Точно так же это может быть тем, что на заборе написано.
Теперь что касается строчек "Dumping virtual table". Дампинг производится три раза. Два раза для таблицы базового класса (из конструктора базового и из конструктора наследника) и один раз для таблицы наследника (из конструктора наследника). Первые два выводы признаны показать, что виртуальные таблицы НЕ МЕНЯЮТСЯ в runtime!
Кто тебе сказал, что они меняются во время выполнения. Я утверждал и настаиваю, что иерархия классов сушествует только во время компияции. После уже ничего не меняется. Виртуальные функции - это механизм времени компиляции.

Сразу после двух ячеек с адресом обработчика ошибки мы видим нулевой адрес. Становится очевидным, что ноль обозначает конец виртуальной таблицы.
Кто тебе сказал, что это терминирующий ноль ? Это что тебе С-строка ?
Надеюсь, пример достаточно нагляден, чтобы решить все споры? :)
Сам разобрал пример, т.е. истолковал всё и спор закончен ?
Оптимизация по скорости:
#define while if
Оптимизация по размеру:
#define struct union
Аватара пользователя
Romeo
Сообщения: 3126
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

То, что утверждаю я, тоже находится в интернете, но оно, в отличие от той статьи, снабжено доказательствами. Я не знаю кто такой Ален Голуб, более того, в той статье не указано, что статья принадлежит именно этому человеку. Да, собственно, мне не важно кто он: тот, кто пишет подобное как специалист не достоин моего уважения.

Почему у тебя хватило времени на то, что прочитать статью непонятно кого, но не хватило времени просмотреть мою небольшую заметку, снабжённую доказательствами? Или ты читаешь только большие статьи? ;)

Книги Страуструпа смотри в интернете. Кстати, на той самой страничке, которую ты давал нам для ознакомления, снизу куча ссылок, в том числе и на главы из книг Страуструпа (кстати, если не знаешь, то это создатель языка С++). К сожалению я сходу не смог найти там ссылки на главы именно о вызове виртуальных функций в конструкторах, но ты можешь погуглить.
&quot писал(а):Кто тебе сказал, что то, что ты печатаешь - таблица виртуальных функций. Точно так же это может быть тем, что на заборе написано.
Эммм, Airhand, твои слова только подтверждают твою необразованность. То, что все компиляторы Microsoft без исключения и ещё по крайней мере 95 процентов других компиляторов всех марок и мастей размещают указатель на таблицу виртуальных функций в первом машинном слове (для Win32 - это четыре байта) объекта - это общеизвестный факт.
&quot писал(а):Кто тебе сказал, что они меняются во время выполнения. Я утверждал и настаиваю, что иерархия классов сушествует только во время компияции. После уже ничего не меняется.
Именно ты, а также автор указанной тобой статьи, утверждаете, что виртуальные таблицы изменяются во время выполнения. Пример показывает, что это не так.
&quot писал(а): Виртуальные функции - это механизм времени компиляции.
Виртуальные функции - это, как раз, механизм времени исполнения. Почитай литературу, в частности, что такое "позднее связывание".
&quot писал(а):Кто тебе сказал, что это терминирующий ноль ? Это что тебе С-строка ?
Посмотрите на этот самолёт. Видите, у него есть руль, как и у моей игрушечной машинки. Вы что, глупы и ничего не понимаете? Никакой это не самолёт - это автомобиль!
&quot писал(а):Сам разобрал пример, т.е. истолковал всё и спор закончен ?
Согласен, я поспешил с выводом, что пример понятен всем. Он понятен только знатокам С++. Собственно, знатокам эта статья и адресовалась.

Airhand, зачем ты споришь, не понимая толком о чём идёт речь? По твоим постам я вижу, что у тебя сейчас в голове каша - это видно и по постоянной неточной или ошибочной терминологии и по тому, как твои взгляды резко меняются от одного сообщения к другому. Возможно, эта каша со временем кристаллизуется в академические знания, но пока ты лишь напрасно надрываешься, пытаясь предстать перед всеми знающим человеком и с каждым постом, сам того не подозревая, всё более оголяя свою невежественность.
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Аватара пользователя
Airhand
Сообщения: 239
Зарегистрирован: 06 окт 2005, 16:21
Откуда: Dnepropetrovsk

То, что утверждаю я, тоже находится в интернете, но оно, в отличие от той статьи, снабжено доказательствами.
Ничем оно не снабжено.
Почему у тебя хватило времени на то, что прочитать статью непонятно кого, но не хватило времени просмотреть мою небольшую заметку, снабжённую доказательствами? Или ты читаешь только большие статьи? ;)
Это ты называеш себя образованным человеком и не знаеш кто такой Ален Голуб ? Я твою заметку даже разобрал, о чём и выписал кучу замечаний.
Книги Страуструпа смотри в интернете. Кстати, на той самой страничке, которую ты давал нам для ознакомления, снизу куча ссылок, в том числе и на главы из книг Страуструпа (кстати, если не знаешь, то это создатель языка С++). К сожалению я сходу не смог найти там ссылки на главы именно о вызове виртуальных функций в конструкторах, но ты можешь погуглить.
Не отсылай меня в инет. Я там смотрел. Если бы там было, то мы бы не спорили. Ты не смог найти потаму, что нету.
Эммм, Airhand, твои слова только подтверждают твою необразованность. То, что все компиляторы Microsoft без исключения и ещё по крайней мере 95 процентов других компиляторов всех марок и мастей размещают указатель на таблицу виртуальных функций в первом машинном слове (для Win32 - это четыре байта) объекта - это общеизвестный факт.
Перешёл на личности, т.к. нет доказательств ? Я хотел только узнать, почему ты решил, чо это vtbl и всё.
Именно ты, а также автор указанной тобой статьи, утверждаете, что виртуальные таблицы изменяются во время выполнения. Пример показывает, что это не так.
Если я это говорил, то приведи цитату. Я с тем же успехом могу сказать, будто бы ты утверждал, что Земля плоская и находится на 4 китах/слонах.
Посмотрите на этот самолёт. Видите, у него есть руль, как и у моей игрушечной машинки. Вы что, глупы и ничего не понимаете? Никакой это не самолёт - это автомобиль!
Я так понял, ты не знаеш что это и только предположил.
Согласен, я поспешил с выводом, что пример понятен всем. Он понятен только знатокам С++. Собственно, знатокам эта статья и адресовалась.

Airhand, зачем ты споришь, не понимая толком о чём идёт речь? По твоим постам я вижу, что у тебя сейчас в голове каша - это видно и по постоянной неточной или ошибочной терминологии и по тому, как твои взгляды резко меняются от одного сообщения к другому. Возможно, эта каша со временем кристаллизуется в академические знания, но пока ты лишь напрасно надрываешься, пытаясь предстать перед всеми знающим человеком и с каждым постом, сам того не подозревая, всё более оголяя свою невежественность.
Опять переход на личности, что нет доказательств ?
Оптимизация по скорости:
#define while if
Оптимизация по размеру:
#define struct union
Аватара пользователя
Romeo
Сообщения: 3126
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

&quot писал(а):Это ты называеш себя образованным человеком и не знаеш кто такой Ален Голуб ? Я твою заметку даже разобрал, о чём и выписал кучу замечаний.
Мы писали сообщения параллельно. Твоей дописки после текста "Добавлено сообщение" ещё не было.

Послушай, я не знал раньше никакого Алана Голуба, а после того, как прочитал его статью, то понял, что он мне не будет интересен и в будущем. Об этом человеке нет статьи на Википедии, он не засветился ни в одном серьёзном труде по C++. Почему ты считаешь, что любому программисту необходимо знать этого человека и, более того, настаиваешь, что если я его не знаю, то не профессионал? А ты знал Алана Голуба до того, как прочёл ту статью?

Может я, конечно, и правда непрофессионал по сравнению с теми же Страуструпом или Мейерсом, но уж никак не от того, что не знаком с Голубом :)
&quot писал(а):Перешёл на личности, т.к. нет доказательств ? Я хотел только узнать, почему ты решил, чо это vtbl и всё.
ОК, на личности больше переходить не буду. То, что это vtl, решал не я, это решили разработчики компилятора VC++.
&quot писал(а):Если я это говорил, то приведи цитату. Я с тем же успехом могу сказать, будто бы ты утверждал, что Земля плоская и находится на 4 китах/слонах.
Хочешь цитату, где ты говорил, что таблица меняется во время исполнения? Таких цитат было много. Вот одна из последних:
&quot писал(а):Я писал о том, что таблица виртуальных функций инициализируется в последнем действии конструктора, а не до него.
Или инициализация не подразумевает внесение значения?
&quot писал(а):Я так понял, ты не знаеш что это и только предположил.
Структура виртуальной таблицы устаканилась уже давно и является идентичной практически на всех компиляторах. Своё сообщение я писал с долей сомнения, постоянно задавая вопросы от несуществующего читателя и придираясь к своим собственным выводам, ради стилистики. Повествование в таком ключе, как правило, позволяет избежать массы вопросов в будущем, так как все они будут оговорены и снабжены ответами в процессе написания заметки. Сам я ни в чём не сомневался. Я могу рассказать и другие тонкости, которые познал с помощью расширения подобной программы в прошлом, но это не будет касаться темы вопроса.
&quot писал(а):что нет доказательств
Доказательство - это программа. Такие низкоуровневые детали реализации могут варьироваться от компилятора к компилятору. Я с самого начала написал, что говорю конкретно о линейке компиляторов от Microsoft. Твой же хвалёный Алан Голуб этого не сделал и, более того, написал несуразицу :)
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Hawk
Сообщения: 216
Зарегистрирован: 17 фев 2004, 14:52
Откуда: СПб
Контактная информация:

Какая жаркая дискуссия.
Я не сдержался. Ни с кем спорить не буду, ничего про это не знаю. Хотел только уточнить одну деталь.

Две цитаты уважаемого Airhand-а
Airhand писал(а):BBB
Противоречие НЕ в том, в какой последовательности вызываются конструкторы. Я писал о том, что таблица виртуальных функций инициализируется в последнем действии конструктора, а не до него.
Airhand писал(а): Вот ссылка по теме: http://www.cyberguru.ru/programming/cpp ... age50.html

Ален Голуб говорит, что таблица виртуальных функций создается/перезаполняется последним действием в конструкторе. Я вообще запутался.
Похоже я единственный (кроме Arihand конечно) почитал эту статью, но к величайшему сожалению не нашел там где же: "Ален Голуб говорит, что таблица виртуальных функций создается/перезаполняется последним действием в конструкторе".

К сожалению я не смог найти доказательств этой занимательной идеи в данной статье. Не могли бы вы уточнить этот момент? Мне очень хотелось бы увидеть это, чисто для себя и может нос кому утереть в следующий раз.
Аватара пользователя
Airhand
Сообщения: 239
Зарегистрирован: 06 окт 2005, 16:21
Откуда: Dnepropetrovsk

Послушай, я не знал раньше никакого Алана Голуба, а после того, как прочитал его статью, то понял, что он мне не будет интересен и в будущем. Об этом человеке нет статьи на Википедии, он не засветился ни в одном серьёзном труде по C++. Почему ты считаешь, что любому программисту необходимо знать этого человека и, более того, настаиваешь, что если я его не знаю, то не профессионал? А ты знал Алана Голуба до того, как прочёл ту статью?
Этим не нужно хвастаться. Это всё равно как если бы литературовед сказал: "Я не знаю никакого Тургенева".
Профессионалы - те люди, которые живут со своей профессии. Тогда уже не специалист. Я этого не утверждал нигде, это всё ты выдумал.
Да я знал книгу Алена Голуба:

[RIGHT]

ВЕРЕВКА ДОСТАТОЧНОЙ ДЛИНЫ, [/RIGHT]



[RIGHT]ЧТОБЫ ВЫСТРЕЛИТЬ СЕБЕ В НОГУ

Правила программирования на С и С++

Ален И. Голуб[/RIGHT]

Или инициализация не подразумевает внесение значения?
Инициализация - это, прежде всего, выделение памяти под объкт. А выделение памяти и внесение изменений в таблицу - это разные вещи.
Структура виртуальной таблицы устаканилась уже давно и является идентичной практически на всех компиляторах. Своё сообщение я писал с долей сомнения, постоянно задавая вопросы от несуществующего читателя и придираясь к своим собственным выводам, ради стилистики. Повествование в таком ключе, как правило, позволяет избежать массы вопросов в будущем, так как все они будут оговорены и снабжены ответами в процессе написания заметки. Сам я ни в чём не сомневался. Я могу рассказать и другие тонкости, которые познал с помощью расширения подобной программы в прошлом, но это не будет касаться темы вопроса.
Может это метка-заполнитель для функции сравнения. Или ты имееш спеку виртульной таблицы ?

Hawk
Похоже я единственный (кроме Arihand конечно) почитал эту статью, но к величайшему сожалению не нашел там где же: "Ален Голуб говорит, что таблица виртуальных функций создается/перезаполняется последним действием в конструкторе".
А вот что сделает с ним компилятор:

Код: Выделить всё

[B]int[/B] _employee__print( employee *this ) { /* ... */ }
[B]int[/B] _employee__cmp ( employee *this, [B]const[/B] storable *ref_r ) { /* ... */ } 
 
_vtab _employee_vtable = 
{
_employee__print, 
_storable_virtf, // [I]Тут нет замещения в производном классе, поэтому[/I] 
// [I]используется указатель на функцию базового класса[/I]. 
_employee_cmp 
};
 
[B]typedef[/B] [B]struct[/B] employee
{
_vtab *_vtable; // [I]Генерируемое компилятором поле данных.[/I] 
[B]int[/B] stuff; // [I]Поле базового класса[/I]. 
[B]int[/B] derived_stuff; // [I]Поле, добавленное в объявлении производного класса[/I]. 
}
employee; 
 
_employee__ctor( employee *this ) // [I]Конструктор по умолчанию, генерируемый[/I]
{ // [I]компилятором.[/I]
_storable_ctor(); // [I]Базовые классы инициализируются[/I]
// [I]в первую очередь[/I].
_vtable = _employee_vtable; // [I]Создается таблица виртуальных функций[/I].
}
Оптимизация по скорости:
#define while if
Оптимизация по размеру:
#define struct union
Закрыто