На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! Правила раздела:
1. Название темы - краткое описание кто/что против кого/чего
2. В первом сообщении - список параметров, по которым идет сравнение.
3. Старайтесь аргументировать свои высказывания. Фразы типа "Венда/Слюникс - ацтой" считаются флудом.
4. Давайте жить дружно и не доводить обсуждение до маразма и личных оскорблений.
Модераторы: Модераторы, Комодераторы
Страницы: (495) « Первая ... 476 477 [478] 479 480 ...  494 495  ( Перейти к последнему сообщению )  
> Delphi vs C++ vs C# , ну и Java немножко, где-то ближе к старшему байту номеров страниц
    Цитата jack128 @
    в коде конструктора никаких заполнений указателей на VMT нету.

    Это можно увидеть в ассемблерном коде.

    Цитата jack128 @
    что технически мешало сделать так:

    Как минимум две вещи:
    1. Этот указатель пришлось бы тащить в качестве неявного параметра конструктора от самого верха иерархии.
    2. Тогда бы возникали ситуации (как раз при вызове виртуальных методов), когда код работает с неинициализированными данными. Это может быть фатально.

    Двухфазная инициализация в Delphi (если её правильно описал Qraizer) от этого спасает. В C++ её аналогом была бы:
    ExpandedWrap disabled
      Child *ch = new Child();
      ch->Init();

    Разработчики на C++ вольны выбирать - какую стратегию инициализацию использовать: однофазную (только на конструкторах) или двухфазную (конструктор + Init-метод).
      Во, видал, jack128? Как только плюсники попробовали мыслить в терминах Дельфи, получилось чёртичё. :)
      Цитата jack128 @
      ...
      Можно на пальцах:


      ExpandedWrap disabled
        struct Base {
          Base() {std::cout << "Base ctor";}
          virtual ~Base() {}
        }
        struct Child { Child() {std::cout << "Child ctor";}}
         
        auto c = new Child();


      в какой момент будет вызван сишный аналог NewInstance ??
      Если абстрактно, то его тут просто нет. Объявив собственный конструктор (по умолчанию), я тем самым отменил предоставляемый. Но если буквально, то конечно это не так, хотя бы потому, что VMT, хоть и не документируется Стандартом, но это таки наиболее очевидный и простой механизм реализации полиморфизма. Это я предупредил, что пишу, не опираясь на документацию по языку, а опираясь на наиболее распространённые реализации языка. Так что сказанное не факт, что имеет место везде и именно таким образом.
      Код конструктора Base (в конкретно этом случае, где нет базовых классов и агрегатов-классов) будет настраивать vptr, что будет сгенерировано компилятором самостоятельно. Затем компилятор сгенерирует код для тела конструктора, что в {}. Собственно всё. Что касается использования, то компилятор просто вызовет operator new(), и затем собственно конструктор. Всё по минимуму, никаких занулений.
      Код деструктора Base сначала будет состоять из его скомпилированного тела, здесь пустого, после чего... ну, конкретно тут - это и всё. Непоказательный пример, в общем.
      Что касается Child, то тут ты видимо опустил его наслодование от Base, да?
      ExpandedWrap disabled
        struct Child : Base { Child() {std::cout << "Child ctor";}}
      Потому что в противном случае он ничем от Base не отличается в общем-то, кроме того, что для него не требуется место в VMT. Так же я не вижу удаления c, а даже если бы и было, то было бы неинтересно, т.к. auto выведет компилятору Child. Вот
      ExpandedWrap disabled
        Base *c = new Child();
        /* ... */
        delete c;
      в этом смысле интереснее. Итак:
      to be continued
      Сообщение отредактировано: Qraizer -
        Цитата Qraizer @
        Что касается Child, то тут ты видимо опустил его наслодование от Base, да?

        да, естественно. опечатка. ждем континуеда.
          Это я за кофе ходил. Продолжаю.
            Qraizer, фигасе, не лень тебе было. Надо признать, по полочкам, если не придираться к терминологии
              Qraizer, :good:
                Цитата D_KEY @
                Qraizer, :good:

                Ага! Это же надо было так долго рассказывать про разницу однофазного и двухфазного конструирования. Но, как говориться, повторение - мать учения. :)
                Сообщение отредактировано: Flex Ferrum -
                  Код конструктора Child будет сначала вызывать конструктор Base, затем настраивать vptr на Child и в заключение содержать код тела Child. Понятно, что только последнее явно определяется написанным программистом между {} в теле Child, первые два действия будут сгенерированы компилятором самостоятельно. Правда, первое действие таково конкретно для этого примера, потому что программист не указал желаемый конструктор базового класса, поэтому компилятор вызвал дефолтовый "сам". В общем случае я могу указать, какой конструктор и с какими параметрами надо вызвать, так что первое действие частично настраивается. Частично - потому что я не могу указать порядок вызовов конструкторов базовых классов и агрегатов, порядок определяется пунктами Стандарта.
                  Итого имеем примерно следующий псевдокод:
                  ExpandedWrap disabled
                    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
                  Note: если в точке X будет брошено исключение, то Child формально ещё не создан, ибо время жизни объекта начинается с момента достижения конца его конструктора, но Base уже есть и полностью готов. Так что деструктор Child не будет вызван, а вот Base будет.
                  Что касается уничтожения, то тут всё соответственно обратным образом. Компилятор предоставляет деструктор ~Child, т.к. программер его не отменил. Ему придётся предоставить его виртуальным, т.к. виртуален деструктор базового класса. Сначала он разместит там (пустое) тело ~Child, затем сменит vptr на Base и вызовет деструктор ~Base. Последний же будет просто состоять из ничего, как уже говорилось, т.к. дальше базовых классов нет.
                  Код, удаляющий c, ввиду позднего связывания деструктора у Base, будет вызывать некий thunk, который вытащит из VMT по переданному this, точнее, по this->vptr, деструктор и смещение для значения this, сообразно сконвертит this к нужному типу (здесь - к Child*) и вызовет сооветствующий деструктор (здесь - Child::~Child). Когда ~Child вернёт управление, thunk просто вызовет operator delete, предварительно скорректировав this.
                  Имеется что-то вроде.
                  ExpandedWrap disabled
                    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);
                    }
                  Примечательно то, что компилятор заботится о том, чтобы автогенерённый им для Child код везде сам заботился о том, чтобы Base не думал о своих производных классах. Base вообще не знает, является ли он полновесным объектом или всего лишь подобъектом чего-то большего. То же справедливо в отношении взаимодействия thunk с полиморфными классами. Конечно, конкретные детали у каждой реализации могут отличаться. Но общая картина в целом будет соответствовать этой.

                  P.S. Я не знаю, где тут провести ассоциации с NewInstance. Я представляю работу объектной модели Дельфи только в общих чертах, без деталей реализации.

                  Добавлено
                  Flex Ferrum, давно уже пора было подбить в одном посту всё надискуссированное. Бо количество "где пруф - где-то было - покажи где, или не было - лень искать" уже перезашкаливает.
                    Цитата Qraizer @
                    Я представляю работу объектной модели Дельфи только в общих чертах, без деталей реализации.


                    Когда мы вызываем конструктор в Delphi, происходит вот что
                    1) Во-первых, есть два способа вызова конструктора. Первый - как классовый метод:
                    ExpandedWrap disabled
                      Obj := TSomeClass.Create;

                    и как instance-метод
                    ExpandedWrap disabled
                      Obj.Create;

                    Если метод вызывается как классовый - переходим к пункту 2, если instance - к пункту 4
                    2) Вызывается NewInstance - классовый метод, объявленный в TObject
                    ExpandedWrap disabled
                      class function TObject.NewInstance: TObject;
                      begin
                        Result := InitInstance(_GetMem(InstanceSize));
                      end;

                    Как видно, метод выделяет на куче память размером InstanceSize и вызывает для нее метод InitInstance. Результат возвращает. Да, этот метод (NewInstance) является виртуальным, что позволяет уже на этом этапе изменить алгоритм создания объекта, но требуется это очень-очень редко. Как пример - синглтон, где реализация NewInstance может сначала проверять, не был ли создан объект ранее, и если был - просто вернуть ссылку на уже созданный экземпляр
                    3) InitInstance проделывает всю подготовительную работу, после которой выделенная память представляет собой чистенький экземпляр: все поля инициализированы нулями и нилами, первое поля указывает на VMT класса и т.д.
                    4) Вызывается код непосредственно метода Create, который пишет программист. Если мы пришли сюда с пункта 1, то он вызывается для ссылки, для которой был вызван, если с пункта 3 - для ссылки, которую вернул NewInstance. Т.е. в принципе мы можем создать объект так (и это тоже будет корректно)
                    ExpandedWrap disabled
                      Obj := TSomeClass.NewInstance;
                      Obj.Create;

                    Внутри конструктора вызов inherited воспринимается как вызов обычного метода, т.е. просто выполняется код унаследованного Create без всех этих неявных действий
                    5) Выполняется AfterConstruction. На этот момент объект уже полностью сконструирован, все унаследованные конструкторы вызваны.

                    Что будет, если на каком-либо этапе произойдет исключение (если память не подводит)...
                    На этапе 5: вызываются последовательно BeforeDestruction, Destroy, FreeInstance
                    На этапе 4: Destroy, FreeInstance
                    На более ранних этапах - просто FreeInstance (если память была вообще выделена)
                    т.е. происходит undo того, что было сделано

                    Выглядит это все примерно так (на коленке):
                    ExpandedWrap disabled
                      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;
                    Сообщение отредактировано: --Ins-- -
                      А BeforeConstruction и AfterDestruction?
                        Цитата korvin @
                        А BeforeConstruction и AfterDestruction?
                        Тебе понравились дельфийские костыли аспектно-ориентированное программирование? :D

                        Добавлено
                        Цитата --Ins-- @
                        InitInstance проделывает всю подготовительную работу, после которой выделенная память представляет собой чистенький экземпляр: все поля инициализированы нулями и нилами
                        Он "чистенький" при условии, если 0 - это "чистенькое значение". А если нет - очень даже "грязненький".

                        Добавлено
                        Цитата Qraizer @
                        Пользователям Билдера отдельный привет, у тех вообще две разные философии присутствуют одновременно, и им следовать надо тоже одновременно. Полный алес, короче.
                        У меня никакого алеса не было. Родного с Delphi/VCL был только редактор форм и реакция на события компонента. AfterConstruction/BeforeDestruction использовал только один раз, в качестве костыля уже не помню к чему.
                          Ну ок. Представим себе гипотетически, что существует объект, одно из значений из которого всегда ненулевое:
                          ExpandedWrap disabled
                            class C {
                                    int i;
                                public:
                                    C() : i(1) {}
                                    bool isValid() { return i != 0; }
                            };


                          Какой смысл для таких объектов в NewInstance, который будет гарантированно возвращать невалидный объект?
                            Цитата trainer @
                            Тебе понравились дельфийские костыли аспектно-ориентированное программирование? :D

                            Нет, но раз уж что-то делать, то делать единообразно, для каждого метода Before- и After- "аспекты".
                              кстати, а в плюсах компиляторы кидают какие нить варнинги/ошибки, если конструктор не все поля объекта инициализирует ??
                              Сообщение отредактировано: jack128 -
                                Цитата jack128 @
                                кстати, а в плюсах компиляторы кидают какие нить варнинги/ошибки, если конструктор не все поля объекта инициализирует ??

                                Поля всегда инициализируются(если ты не указываешь явно, то конструктором по умолчанию). За исключением встроенных типов, вроде int(непонятно почему :'( ). Их нужно явно инициализировать, при чем большинство компиляторов варнинг не выдает(хотя может ключики есть отдельные)...
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:
                                Страницы: (495) « Первая ... 476 477 [478] 479 480 ...  494 495


                                Рейтинг@Mail.ru
                                [ Script execution time: 0.7218 ]   [ 15 queries used ]   [ Generated: 28.07.26, 17:36 GMT ]