Delphi vs C++ vs C#
, ну и Java немножко, где-то ближе к старшему байту номеров страниц
![]() |
Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
|
| ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
| [216.73.216.192] |
|
|
Правила раздела:
| Страницы: (495) « Первая ... 476 477 [478] 479 480 ... 494 495 ( Перейти к последнему сообщению ) |
Delphi vs C++ vs C#
, ну и Java немножко, где-то ближе к старшему байту номеров страниц
|
Сообщ.
#7156
,
|
|
|
|
Это можно увидеть в ассемблерном коде. Как минимум две вещи: 1. Этот указатель пришлось бы тащить в качестве неявного параметра конструктора от самого верха иерархии. 2. Тогда бы возникали ситуации (как раз при вызове виртуальных методов), когда код работает с неинициализированными данными. Это может быть фатально. Двухфазная инициализация в Delphi (если её правильно описал Qraizer) от этого спасает. В C++ её аналогом была бы: ![]() ![]() Child *ch = new Child(); ch->Init(); Разработчики на C++ вольны выбирать - какую стратегию инициализацию использовать: однофазную (только на конструкторах) или двухфазную (конструктор + Init-метод). |
|
Сообщ.
#7157
,
|
|
|
|
Во, видал, jack128? Как только плюсники попробовали мыслить в терминах Дельфи, получилось чёртичё.
![]() Цитата jack128 @ Если абстрактно, то его тут просто нет. Объявив собственный конструктор (по умолчанию), я тем самым отменил предоставляемый. Но если буквально, то конечно это не так, хотя бы потому, что VMT, хоть и не документируется Стандартом, но это таки наиболее очевидный и простой механизм реализации полиморфизма. Это я предупредил, что пишу, не опираясь на документацию по языку, а опираясь на наиболее распространённые реализации языка. Так что сказанное не факт, что имеет место везде и именно таким образом.... Можно на пальцах: ![]() ![]() struct Base { Base() {std::cout << "Base ctor";} virtual ~Base() {} } struct Child { Child() {std::cout << "Child ctor";}} auto c = new Child(); в какой момент будет вызван сишный аналог NewInstance ?? Код конструктора Base (в конкретно этом случае, где нет базовых классов и агрегатов-классов) будет настраивать vptr, что будет сгенерировано компилятором самостоятельно. Затем компилятор сгенерирует код для тела конструктора, что в {}. Собственно всё. Что касается использования, то компилятор просто вызовет operator new(), и затем собственно конструктор. Всё по минимуму, никаких занулений. Код деструктора Base сначала будет состоять из его скомпилированного тела, здесь пустого, после чего... ну, конкретно тут - это и всё. Непоказательный пример, в общем. Что касается Child, то тут ты видимо опустил его наслодование от Base, да? ![]() ![]() struct Child : Base { Child() {std::cout << "Child ctor";}} ![]() ![]() Base *c = new Child(); /* ... */ delete c; to be continued |
|
Сообщ.
#7158
,
|
|
|
|
Цитата Qraizer @ Что касается Child, то тут ты видимо опустил его наслодование от Base, да? да, естественно. опечатка. ждем континуеда. |
|
Сообщ.
#7159
,
|
|
|
|
Это я за кофе ходил. Продолжаю.
|
|
Сообщ.
#7160
,
|
|
|
|
Qraizer, фигасе, не лень тебе было. Надо признать, по полочкам, если не придираться к терминологии
|
|
Сообщ.
#7161
,
|
|
|
|
Qraizer,
|
|
Сообщ.
#7162
,
|
|
|
|
Цитата D_KEY @ Qraizer, Ага! Это же надо было так долго рассказывать про разницу однофазного и двухфазного конструирования. Но, как говориться, повторение - мать учения. |
|
Сообщ.
#7163
,
|
|
|
|
Код конструктора Child будет сначала вызывать конструктор Base, затем настраивать vptr на Child и в заключение содержать код тела Child. Понятно, что только последнее явно определяется написанным программистом между {} в теле Child, первые два действия будут сгенерированы компилятором самостоятельно. Правда, первое действие таково конкретно для этого примера, потому что программист не указал желаемый конструктор базового класса, поэтому компилятор вызвал дефолтовый "сам". В общем случае я могу указать, какой конструктор и с какими параметрами надо вызвать, так что первое действие частично настраивается. Частично - потому что я не могу указать порядок вызовов конструкторов базовых классов и агрегатов, порядок определяется пунктами Стандарта.
Итого имеем примерно следующий псевдокод: ![]() ![]() this = new (sizeof(Child)); // this указывает на мусор Child::Child() { Base::Base(static_cast<Base*>(this)); { vptr = typeid(Base); // this указывает на Base, но экземпляр ещё не создан { std::cout << "Base ctor"; } } // this указывает на Base, экземпляр полностью готов к использованию как Base vptr = typeid(Child); // this указывает на Child, но экземпляр как Child ещё не создан, он пока лишь только Base { std::cout << "Child ctor";// Точка X } } // this указывает на Child, экземпляр полностью готов к использованию как Child Что касается уничтожения, то тут всё соответственно обратным образом. Компилятор предоставляет деструктор ~Child, т.к. программер его не отменил. Ему придётся предоставить его виртуальным, т.к. виртуален деструктор базового класса. Сначала он разместит там (пустое) тело ~Child, затем сменит vptr на Base и вызовет деструктор ~Base. Последний же будет просто состоять из ничего, как уже говорилось, т.к. дальше базовых классов нет. Код, удаляющий c, ввиду позднего связывания деструктора у Base, будет вызывать некий thunk, который вытащит из VMT по переданному this, точнее, по this->vptr, деструктор и смещение для значения this, сообразно сконвертит this к нужному типу (здесь - к Child*) и вызовет сооветствующий деструктор (здесь - Child::~Child). Когда ~Child вернёт управление, thunk просто вызовет operator delete, предварительно скорректировав this. Имеется что-то вроде. ![]() ![]() thunk(this) { typeinfo *info_t = VMT[this->vptr]; info_t->dtor(static_cast<info_t->dyn_type*>(this)); /* косвенно попадаем в Child::~Child */ Child::~Child() { // Здесь кончается время жизни Child, но не Base; однако динамический тип объекта всё ещё Child { /* (в данном случае) пустое дело деструктора */ } this = typeid(Base); // Здесь динамический тип объекта становится Base Base::Base(); { // Здесь кончается время жизни Base; динамический тип объекта тоже всё ещё Base { /* в данном случае тоже пустое дело деструктора */ } } // формально тут this указывает уже на мусор } delete info_t->head_of_obj(this); } P.S. Я не знаю, где тут провести ассоциации с NewInstance. Я представляю работу объектной модели Дельфи только в общих чертах, без деталей реализации. Добавлено Flex Ferrum, давно уже пора было подбить в одном посту всё надискуссированное. Бо количество "где пруф - где-то было - покажи где, или не было - лень искать" уже перезашкаливает. |
|
Сообщ.
#7164
,
|
|
|
|
Цитата Qraizer @ Я представляю работу объектной модели Дельфи только в общих чертах, без деталей реализации. Когда мы вызываем конструктор в Delphi, происходит вот что 1) Во-первых, есть два способа вызова конструктора. Первый - как классовый метод: ![]() ![]() Obj := TSomeClass.Create; и как instance-метод ![]() ![]() Obj.Create; Если метод вызывается как классовый - переходим к пункту 2, если instance - к пункту 4 2) Вызывается NewInstance - классовый метод, объявленный в TObject ![]() ![]() class function TObject.NewInstance: TObject; begin Result := InitInstance(_GetMem(InstanceSize)); end; Как видно, метод выделяет на куче память размером InstanceSize и вызывает для нее метод InitInstance. Результат возвращает. Да, этот метод (NewInstance) является виртуальным, что позволяет уже на этом этапе изменить алгоритм создания объекта, но требуется это очень-очень редко. Как пример - синглтон, где реализация NewInstance может сначала проверять, не был ли создан объект ранее, и если был - просто вернуть ссылку на уже созданный экземпляр 3) InitInstance проделывает всю подготовительную работу, после которой выделенная память представляет собой чистенький экземпляр: все поля инициализированы нулями и нилами, первое поля указывает на VMT класса и т.д. 4) Вызывается код непосредственно метода Create, который пишет программист. Если мы пришли сюда с пункта 1, то он вызывается для ссылки, для которой был вызван, если с пункта 3 - для ссылки, которую вернул NewInstance. Т.е. в принципе мы можем создать объект так (и это тоже будет корректно) ![]() ![]() Obj := TSomeClass.NewInstance; Obj.Create; Внутри конструктора вызов inherited воспринимается как вызов обычного метода, т.е. просто выполняется код унаследованного Create без всех этих неявных действий 5) Выполняется AfterConstruction. На этот момент объект уже полностью сконструирован, все унаследованные конструкторы вызваны. Что будет, если на каком-либо этапе произойдет исключение (если память не подводит)... На этапе 5: вызываются последовательно BeforeDestruction, Destroy, FreeInstance На этапе 4: Destroy, FreeInstance На более ранних этапах - просто FreeInstance (если память была вообще выделена) т.е. происходит undo того, что было сделано Выглядит это все примерно так (на коленке): ![]() ![]() try Obj := TSomeClass.NewInstance; try Obj.Create; try Obj.AfterConstruction; except Obj.BeforeDestruction; raise; end; except Obj.Destroy; raise; end; except Obj.FreeInstance; raise; end; |
|
Сообщ.
#7165
,
|
|
|
|
А BeforeConstruction и AfterDestruction?
|
|
Сообщ.
#7166
,
|
|
|
|
Цитата korvin @ Тебе понравились дельфийские А BeforeConstruction и AfterDestruction? Добавлено Цитата --Ins-- @ Он "чистенький" при условии, если 0 - это "чистенькое значение". А если нет - очень даже "грязненький". InitInstance проделывает всю подготовительную работу, после которой выделенная память представляет собой чистенький экземпляр: все поля инициализированы нулями и нилами Добавлено Цитата Qraizer @ У меня никакого алеса не было. Родного с Delphi/VCL был только редактор форм и реакция на события компонента. AfterConstruction/BeforeDestruction использовал только один раз, в качестве костыля уже не помню к чему. Пользователям Билдера отдельный привет, у тех вообще две разные философии присутствуют одновременно, и им следовать надо тоже одновременно. Полный алес, короче. |
|
Сообщ.
#7167
,
|
|
|
|
Ну ок. Представим себе гипотетически, что существует объект, одно из значений из которого всегда ненулевое:
![]() ![]() class C { int i; public: C() : i(1) {} bool isValid() { return i != 0; } }; Какой смысл для таких объектов в NewInstance, который будет гарантированно возвращать невалидный объект? |
|
Сообщ.
#7168
,
|
|
|
|
Цитата trainer @ Тебе понравились дельфийские ![]() Нет, но раз уж что-то делать, то делать единообразно, для каждого метода Before- и After- "аспекты". |
|
Сообщ.
#7169
,
|
|
|
|
кстати, а в плюсах компиляторы кидают какие нить варнинги/ошибки, если конструктор не все поля объекта инициализирует ??
|
|
Сообщ.
#7170
,
|
|
|
|
Цитата jack128 @ кстати, а в плюсах компиляторы кидают какие нить варнинги/ошибки, если конструктор не все поля объекта инициализирует ?? Поля всегда инициализируются(если ты не указываешь явно, то конструктором по умолчанию). За исключением встроенных типов, вроде int(непонятно почему ). Их нужно явно инициализировать, при чем большинство компиляторов варнинг не выдает(хотя может ключики есть отдельные)... |