На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Qraizer, Hsilgos
  
> насколько тормозной dynamic_cast?
    Здравствуйте!

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

    Надо привести указатель класса Alpha, к указателю класса Beta.

    ExpandedWrap disabled
      // базовый
      class alpha abstract
      {
      public:
      virtual ~Alpha()=0;
      virtual int Category() const = 0;
      };
       
      // наследник
      class Beta:
      public virtual Alpha
      {
      public:
      ~Beta(){}
      int Category() const override { return 123; }
      };
       
      Beta* AsBeta( Alpha* ptr )
      {
       
      if( ptr )
      {
      if( 123 == ptr->Category() )
      {
      return dynamic_cast< Beta* >( ptr );
      }
      }
       
      throw bad_cast();
      }


    Это очень упрощённый набросок моего кода. Оригинал работает, но смущает.

    Меня же интересует именно dynamic_cast. Насколько он медленный?
    Вроде бы, он сравнивает типы, проверкой равенства строк их названий.
    Но в данном случае, в объектах могут быть идинтефикаторы (указатели на vftable) или же их недостаточно?

    Можно ли его заменить на static_cast?
    Как я понимаю, объект базового класса, размещён по смещению, от начала дочернего. А значит static_cast или тем более reinterpret_cast будут указывать совсем не туда?

    Поскольку наследование virtual, то пришлось взять именно dynamic_cast. Без виратуального наследования, нормально работает static_cast. Но мне нужно примешать интерфейс...

    Всякие мысли, о том, как переделать архитектуру, подсказывают правильное, но черезвычайно муторное решение.
    Я знаю, что юзать dynamic_cast совсем не круто, но насколько это плохо?
    Сообщение отредактировано: Eric-S -
      Цитата Eric-S @
      Насколько он медленный?

      Я думаю тебе поможет дизассемблер, ссылка есть в объявлениях раздела. Сделай синтетический пример для динамического и статического кастов, и посмотри что генерируется. Для беглой оценки - сойдет. Если нужно более точно, то тогда чисто 10-15 минутные тесты с использованием таймера высокого разрешения.
        Цитата Eric-S @
        Поскольку наследование virtual, то пришлось взять именно dynamic_cast. Без виратуального наследования, нормально работает static_cast.
        static_cast<> нормально работает всегда, когда и dynamic_cast<>, при условии, что компилятору доступна архитектура иерархии. Т.е. просто на опережающих объявлениях, например, static_cast<> обломается, а dynamic_cast<> вывезет.
        Единственное важное отличие заключается в том, что static_cast<> работает исключительно со статическим типом объекта, а значит динамический его тип ему не известен, поэтому в результате каста можешь получить UB, если в run-time окажется, что переданный объект нужного подобъекта не содержит. А вот dynamic_cast<> работает именно в run-time и ищет затребованный подобъект в переданном объекте, т.е. делает то же самое, но с проверкой успеха.
        Скорость зависит от величины иерархии. dynamic_cast<> может ходить не только вниз, но и вбок в случае множественного наследования. Фактически там осуществляется обход графа. Конечно, необязательно полный, но в случае фэйла он таковым получится. Ну и от качества реализации зависит, обычно компилятор никакого кода не генерирует, кроме вызова служебной библиотечной функции. Зато он генерирует сам граф, построенный на RTTI по каждому классу иерархии.
        Ты всегда можешь ограничиться static_cast<>, если имеешь гарантию того, что целевой класс точно имеется в исходном объекте. Например, в этом случае
        ExpandedWrap disabled
          std::ostringstream os;
          std::string buf = static_cast<std::ostringstream&>(os << std::dec << "Code error is " << GetLastError() << std::hex << " (0x" << std::setw(4) << GetLastError() << ')').str();
        иначе и быть не может. Но вот в твоём случае не факт.
        Сообщение отредактировано: JoeUser -
          Цитата JoeUser @
          Я думаю тебе поможет дизассемблер

          Он поможет изучить конкретную реализацию.
          Но... Гы-ха-эм... Со static_cast вообще не компилируется.
          Тут вопрос стоит в иной плоскости. Оставить ли мне dyanamic_cast или навернуть архитектуру до полного выноса мозга, чтоб избавиться от приведений?
            Цитата Eric-S @
            Со static_cast вообще не компилируется.

            Покажи синтетический пример кода.
              Цитата Qraizer @
              static_cast<> нормально работает всегда, когда и dynamic_cast<>, при условии, что компилятору доступа архитектура иерархии.

              Понятно. В моём случае, там ещё сложнее. На самом деле я пробовал static_pointer_cast, поскольку у меня указатели shared_ptr.
              Приведение выполнял в коде, где доступны полные описания иерархии классов.
              Но компилятор msvs 2017, заявил что не может делать статическое приведение при виртуальном наследовании.
                Цитата JoeUser @
                Покажи синтетический пример кода.

                Ну вот например https://ideone.com/H30Gbv

                ExpandedWrap disabled
                  #include <cassert>
                  #include <iostream>
                  #include <memory>
                   
                  using namespace std;
                   
                   
                  enum class ObjectCategory
                      {
                      none,
                      XObject,
                      YObject,
                      XYObject
                      };
                   
                   
                  class ObjectBase:
                      public enable_shared_from_this< ObjectBase >
                      {
                      public:
                   
                          ObjectBase() {}
                   
                          virtual ~ObjectBase() {}
                   
                          virtual ObjectCategory Category() const = 0;
                   
                      };
                   
                  class XObject:
                      public virtual ObjectBase
                      {
                      public:
                   
                          XObject() {}
                   
                          ~XObject() {}
                   
                          ObjectCategory Category() const override
                              {
                              return ObjectCategory::XObject;
                              }
                   
                      };
                   
                  int main()
                      {
                      typedef shared_ptr< ObjectBase > ObjectPtr;
                   
                      ObjectPtr p1 = make_shared< XObject >();
                      assert( ObjectCategory::XObject == p1->Category() );
                   
                      auto x1 = static_pointer_cast< XObject, ObjectBase >( p1 );
                      assert( x1 );
                   
                      return EXIT_SUCCESS;
                      }
                  Цитата Qraizer @
                  Ты всегда можешь ограничиться static_cast<>, если имеешь гарантию того, что целевой класс точно имеется в исходном объекте.
                  ...
                  Но вот в твоём случае не факт.

                  В моём случае, реальном, а не ситетическом, такие гарантии есть. См метод Category().
                  Каждый класс перегружает его, а объект возвращает уникальный идентификатор класса.
                  Впрочем, даже это сделано для подстраховки.
                  Сам контекст предполагает, что переданн заведомо коректный объект.
                  Впрочем, если это не так, то это уже фатальная ошибка. Она должна вылезти в модульных тестах.

                  Да, действительно, было бы хорошо использовать строгую типизацию, без всяких приведений.
                  Но по сути, мне там нужен навороченный мультиметод. Я же попытался обойтись паттерном "visitor". Причём сделал общий интерфейс. Именно из-за интерфейса ломается типизация.
                    Eric-S, есть тема. Возможно я еще не дорос, не осознал всю глубину глубин. Но смею предположить, что если отделить реализацию классов от умных указателей - то твоя композиция классов значительно упростится.

                    Твой пост навёл меня на гугление в лайт-стиле, и я нашел статью. На первый взгляд "обнять и плакать". "Улучшили в одном, усложнили в десяти". Это первое впечатление. Конечно же допускаю, что я не прав в квадрате. Но чуйка нашептывает ... "помни о Бритве Оккама".
                      Цитата JoeUser @
                      и я нашел статью. На первый взгляд "обнять и плакать". "Улучшили в одном, усложнили в десяти".

                      Статья старая. Даже как-то давал на неё ссылку. Но всё равно, сейчас, пролистал, мало ли чего-то забыл.

                      Но для начала, зачем вообще нужны умные указатели?
                      Чтобы гарантировать разрушение объекта и не думать о нём.
                      Использование конечно неоднозначное, но без них гораздо хуже и сложнее.

                      А теперь разберу по пунктам.

                      1. Перекрестные ссылки
                      Угу. Проблема известная. Но именно в C++ решается нагляднее и однозначнее, чем в прочих языках.
                      В тех же C#, Java и других языках, об этом тоже надо знать, да ещё и исхитриться применить.
                      Я знаю. Я делаю слабые ссылки. Ничего сложного нет. Надо лишь запомнить, что на владельца ссылка должна быть слабой.

                      2. Безымянные указатели
                      Странное название, описание кривое... Впрочем, действительно такая проблемка есть.
                      Но решается простым использованием make-функций везде и всегда. Это и для производительности полезно.
                      Я везде делаю make_shared или ещё чего-нибудь такое.
                      Вообще new и delete, встречаются в старом, спецефичном или новичкковом коде.
                      https://habrahabr.ru/company/aligntechnology/blog/283352/

                      3. Проблема использования в разных потоках
                      Угу, есть такая проблемка. Но она косается вообще любых указателей, данных и так далее. Это просто напоминалка, что shared_ptr так же страдает, как и все.

                      4. Особенности времени разрушения освобождающего функтора для shared_ptr
                      Очередная напоминалка. Мало ли кто-то забудет, где-то почистить.

                      5. Особенности работы с шаблоном enable_shared_from_this
                      Это не проблема, а разъяснение особенности поведения. Надо учитывать. Точно так же как учитываем вызов виртуальных методов из конструкторов и деструкторов.
                      Я это знаю. Ну не надо этого вызывать в конструкторах и деструкторах, а то неопределённое поведение замучит.

                      Ну и заключение. Автор рекомендовал ознакомится с внутренней реализацией и понять поведение. Он конечно же прав. Поработал капитаном "Очевидность". Молодца!

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

                      Добавлено
                      Цитата JoeUser @
                      Но чуйка нашептывает ... "помни о Бритве Оккама".

                      Отрезать лишнее? Ну... Где-то конечно можно.
                      А в целом, всё равно умные указатели очень нужны. Если их отрезать, точно придётся делать свой велосипед... Впрочем, он у меня где-то пылится, ндо найти... Но там всё тоже самое. Правда лунопарк без шлюх и блэкджека.
                        Цитата Eric-S @
                        Но компилятор msvs 2017, заявил что не может делать статическое приведение при виртуальном наследовании.
                        Хм... впрочем, возможно некоторые оптимизации размещения ему мешают. В ряде случаев действительно, не зная динамического типа, смещение до виртуальной базы не определить.

                        Добавлено
                        Цитата Eric-S @
                        В моём случае, реальном, а не ситетическом, такие гарантии есть. См метод Category().
                        Это не гарантия. Ты (ошибочно?) можешь назначить одинаковые категории разным классам.

                        Добавлено
                        Цитата Eric-S @
                        Да, действительно, было бы хорошо использовать строгую типизацию, без всяких приведений.
                        Но по сути, мне там нужен навороченный мультиметод. Я же попытался обойтись паттерном "visitor". Причём сделал общий интерфейс. Именно из-за интерфейса ломается типизация.
                        Приведение не обязательно "ломает типизацию". dynamic_cast<> вообще можно рассматривать как селектор интерфейсов при нескольких их реализованных в классе.
                        Конкретно по мультиметодам можешь посмотреть мою тему по ним. Там очень высокая производительность, без dynamic_cast<> и тоже visitor.
                          Цитата Qraizer @
                          Конкретно по мультиметодам можешь посмотреть мою тему по ним. Там очень высокая производительность, без dynamic_cast<> и тоже visitor.

                          В который раз пытаюсь осилить ту реализацию Мультиметоды

                          Очень интересно... И не всё понятно.
                          Но это не совсем мой случай. Мне бы настройку мультиметодов упростить. Там как-то сложно.

                          Вообще у меня, не совсем чистый мультиметод. А упрощённая вариация, где типы, более мене попарно связаны. Хотя. делается это через три виртуальных метода.

                          У меня параметром виртуальной функции передаётся объект менеджера объектов, который либо создаёт новый объект, либо достаёт его из пула. Этот менеджер заменяет второй список параметров в мультиметоде.

                          ExpandedWrap disabled
                            result_type XObject::action( const manager_type& manager ) override
                            {
                             
                             
                            // получить провайдер действия для объекта класса XObject
                            IProvider* provider =  manager.XObject();
                             
                            // выполнить действие над объектом
                            return provider->Action( this );
                            }


                          Менеджер возвращает объект с интерфейсом IProvider.

                          ExpandedWrap disabled
                            class IProvider
                            {
                            public:
                            virtual result_type Action( ObjectBase* ptr ) = 0;
                            };


                          Есть множество наследников ObjectBase, и для каждого есть несколько вариантов наследующих IProvider. Менеджер обобщает и пытается свести воедино.

                          Но метод IProvider::Action() принимает только тип ObjectBase*
                          Вот в этом самом месте ломается типизация.

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

                          Я собственно и поднял данную тему, чтобы понять, стоит ли мне заморочится с отдельными интерфейсами, или же можно оставить динамическое приведение?
                          Сообщение отредактировано: Eric-S -
                            Цитата Eric-S @
                            Но это не совсем мой случай. Мне бы настройку мультиметодов упростить. Там как-то сложно.
                            А что там непонятно? Спросил бы.
                            Цитата Eric-S @
                            Вообще у меня, не совсем чистый мультиметод. А упрощённая вариация, где типы, более мене попарно связаны.
                            Что-то я тут мультиметода и не увидел даже. Как и необходимости в приведении типов вообще.
                              Цитата Qraizer @
                              Что-то я тут мультиметода и не увидел даже. Как и необходимости в приведении типов вообще.

                              У меня мультиметод это Action. Если убрать все дополнения, то в чистом виде будут два параметра:
                              result_type Action( IObject* o, IProvider* p );

                              IObject это множество классов описывающих объект.
                              IProvider это другое множество классов выполняющих действие с объектом.

                              Правда, один конкретный IProvider может выполнять действие лишь с одним лини нисколькими типами объектов. Так же для одного типа IObject подходит один или несколько провайдеров. Но всё равно, разнообразных сочетаний очень много.
                              Но я не стал реальзовывать мультиметод в чистом и явном виде.
                              IObject интерфейс класса с виртуальным методом Action, который принимает фабрику провайдеров.
                              IProvider интерфейс класса с виртуальным методом Action, который принимает указатель на IObject.

                              Вся эта фигня, маскирует мультиметод. Тем более он далеко не чистый.


                              На счёт приведения, то оно нужно немного дальше.

                              ExpandedWrap disabled
                                // интерфейс провайдера
                                class IProvider
                                {
                                public:
                                 
                                virtual ~IProvider() = 0;
                                 
                                virtual result_type Action( IObject* ptr ) = 0;
                                 
                                };
                                 
                                 
                                // реализация провайдера
                                class XProvider:
                                public IProvider
                                {
                                public:
                                 
                                XProvider() {}
                                 
                                ~XProvider() {}
                                 
                                result_type Action( IObject* ptr ) override
                                {
                                return Work( dynamic_cast< XObject* >( ptr );
                                }
                                 
                                private:
                                 
                                // действия специфичные для XObject
                                result_type Work( XObject* ptr )
                                {
                                ptr->Work();
                                }
                                 
                                };


                              Информация о типе теряется при вызове IProvider::Action(). А затем её приходится востанавливать.

                              В принципе, если наделать интерфейсов для каждого типа Object... То можно обойтись вообще без приведения.

                              Ладно, сам интерфейс можно сделать шаблонным. Это не так страшно.

                              Но вот прописывать это всё в общем менеджере меня откровенно пугает.

                              Впрочем, решил попробовать, сейчас как раз занимаюсь переделкой базового класса провайдеров.

                              Добавлено
                              Цитата Qraizer @
                              А что там непонятно? Спросил бы.

                              Хэх. На самом деле мне понятны лишь отдельные куски. А вот общего понимания не возникло.
                              Что там вообще происходит?
                              Зачем это нужно?
                              Почему именно так, а не иначе?

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

                              Вообще, я вынужден признаться, что это пока не мой уровень.
                                Цитата Eric-S @
                                Я знаю, что юзать dynamic_cast совсем не круто, но насколько это плохо?

                                Почему не круто? Идеальных архитектур не бывает, а dynamic_cast вполне себе безопасный вариант каста. Значит не так и плохо его юзать.

                                Просто static_cast сделает тебе приведение типов еще на этапе компиляции. А dynamic_cast - сработает в рантайме, если используются динамические типы. Ну и void* еще кастует. Т.е. если сравнивать его со static_cast, то static_cast'а во время выполнения программы не происходит. А dynamic_cast происходит. И на это теряется время. Сколько времени - выше Qraizer в кратце рассказал.

                                Цитата Eric-S @
                                Поскольку наследование virtual, то пришлось взять именно dynamic_cast.

                                Виртуальное наследование нужно для разруливания парадоксов при использовании множественного наследования, например когда у тебя в архитектуре получается ромбовидное наследование, чтобы решить проблему повторного вызова конструктора базового класса, при создании объекта дочернего класса. Но у тебя я пока не вижу смысла делать виртуальное наследование. У тебя даже множественного наследования нет.
                                dynamic_cast юзается когда у тебя работает динамический полиморфизм. Т.е. ты оперируешь указателем на объект класса, в котором есть хотя бы одна виртуальная функция.

                                Добавлено
                                Цитата Eric-S @
                                Без виратуального наследования, нормально работает static_cast.

                                В Debug сборке? Я бы не стал там юзать static_cast, т.к. у тебя полиморфный тип. Попробуй компильнуть в Release режиме с полной оптимизацией и чуть усложни архитектуру, и вылетишь с каким нибудь AV.
                                Сообщение отредактировано: KILLER -
                                  Цитата KILLER @
                                  Но у тебя я пока не вижу смысла делать виртуальное наследование. У тебя даже множественного наследования нет.

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

                                  Добавлено
                                  Цитата KILLER @
                                  В Debug сборке? Я бы не стал там юзать static_cast, т.к. у тебя полиморфный тип. Попробуй компильнуть в Release режиме с полной оптимизацией и чуть усложни архитектуру, и вылетишь с каким нибудь AV.

                                  Угу. Сейчас именно debug сборка. А что и где может вылетить? Я не понял, в чём проблема?
                                  Сейчас компильнул, всё вроде ок... Правда у меня боевой нагрузки нет, только модульные тесты.

                                  Добавлено
                                  Цитата KILLER @
                                  Почему не круто? Идеальных архитектур не бывает, а dynamic_cast вполне себе безопасный вариант каста. Значит не так и плохо его юзать.

                                  Люди говорят, что не круто. А вот насколько это некруто, мне как раз, хотелось бы выяснить.
                                    Цитата Eric-S @
                                    А что и где может вылетить? Я не понял, в чём проблема?
                                    Сейчас компильнул, всё вроде ок... Правда у меня боевой нагрузки нет, только модульные тесты.

                                    Ну на сколько я понимаю, я могу конечно ошибаться, но все же. Когда ты юзаешь динамические полиморфные типы(классы с виртуальными функциями), то компилятор создает для каждого полиморфного объекта - VMT(Таблицу Виртуальных Методов). И эта таблица строиться как раз на основании архитектуры наследования, причем строится она конкретно в момент создания объекта динамически полиморфного типа, т.е. в рантайме, да еще и в куче, и именно ей пользуется dynamic_cast, для каста объектов разных типов. А static_cast - кастит/не кастит на этапе компиляции. Т.к. у тебя абстрактный класс, от которого наследуется дочерний, значит для них будет сгенерирована VMT. И я не думаю что static_cast вообще знает о ее существовании, т.к. он работает совершенно в другом измерении. Возможно в Debug сборке и при простой архитектуре, компилятор еще на этапе компиляции видит косяк, понимает что тут ошибка, и подменяет static_cast каким нибудь dynamic_cast, или просто срабатывает каст, как если бы у тебя не было виртуальных методов. Но мне кажется что при полной оптимизации и немного усложненной архитектуре, если не вылетит ошибка компиляции, то это еще не значит что static_cast будет работать всегда стабильно.

                                    Цитата Eric-S @
                                    Люди говорят, что не круто. А вот насколько это некруто, мне как раз, хотелось бы выяснить.

                                    А чем они аргументируют что это не круто? Если юзать dynamic_cast не круто, то динамический полиморфизм(то есть виртуальные методы) - тем более юзать не круто. Другими словами dynamic_cast юзать не круто, на столько же, на сколько не круто юзать динамический полиморфизм и RTTI. Так как ты получаешь некие преимущества гибкости при разработке программы, но жертвуешь памятью и скоростью.
                                    Потому как статический полиморфизм - работает на этапе компиляции - явный пример - Шаблоны. Все происходит на этапе компиляции, поэтому возрастает объем используемой памяти и падает производительность - только компиляции проекта, а на скорость выполнения программы - это никак не влияет. А вот динамический полиморфизм - работает только в рантайме! А соответственно он более тяжел, чем его брат статический полиморфизм. И памяти он больше жрет, и по скорости проседаешь.
                                    Соответственно - если ты выбрал использование динамического полиморфизма, значит использовать dynamic_cast - вполне нормально. И более того, это безопасно, т.к. можно проверить результат операции. Другое дело - если, как написал выше Qraizer, у тебя есть злоупотребление этими кастами - то значит что в твоей архитектуре что то не так.
                                    Сообщение отредактировано: KILLER -
                                      Цитата KILLER @
                                      А static_cast - кастит/не кастит на этапе компиляции. Т.к. у тебя абстрактный класс, от которого наследуется дочерний, значит для них будет сгенерирована VMT. И я не думаю что static_cast вообще знает о ее существовании, т.к. он работает совершенно в другом измерении. Возможно в Debug сборке и при простой архитектуре, компилятор еще на этапе компиляции видит косяк, понимает что тут ошибка, и подменяет static_cast каким нибудь dynamic_cast, или просто срабатывает каст, как если бы у тебя не было виртуальных методов. Но мне кажется что при полной оптимизации и немного усложненной архитектуре, если не вылетит ошибка компиляции, то это еще не значит что static_cast будет работать всегда стабильно.

                                      Я ведь написал, что в случае с виртуальным наследованием, static_cast вообще не компилируется! Пришлось поставить именно dynamic_cast. Тут без вариантов.
                                      И как оно примерно работает, я представляю.
                                      Правда, вот некоторые люди утверждают, что microsoft и некоторые другие реализации, выполняют проверку типов, по их символьным именам.

                                      Добавлено
                                      Цитата KILLER @
                                      А чем они аргументируют что это не круто?

                                      Серьёзной просадкой производительности.

                                      Добавлено
                                      Цитата KILLER @
                                      Другое дело - если, как написал выше Qraizer, у тебя есть злоупотребление этими кастами - то значит что в твоей архитектуре что то не так.

                                      Я это знаю. Об этом оговорился сразу. Архитектура это отдельная проблема. Потихоньку решаю.
                                        Цитата Eric-S @
                                        Я ведь написал, что в случае с виртуальным наследованием, static_cast вообще не компилируется!

                                        Могу лишь предположить, что при виртуальном наследовании создается какая нибудь виртуальная таблица классов, и компилятор на этапе компиляции понимает что static_cast тут применить в принципе не получится. А когда у тебя нет виртуального наследования, но есть виртуальные методы, и кастишь ты указатель на объект, то кастит этот static_cast так как умеет, т.е. твои виртуальные методы работать не будут. И получишь ты какой нибудь pure virtual call, но опять же повторюсь - это мое предположение. Я не уверен в том, что я прав на 100%.

                                        Цитата Eric-S @
                                        И как оно примерно работает, я представляю.

                                        Это все понятно. Но ответь на вопрос, мне просто реально интересно, чем аргументируют?
                                        Вот вопрос:
                                        Цитата KILLER @
                                        А чем они аргументируют что это не круто?
                                          Цитата KILLER @
                                          Соответственно - если ты выбрал использование динамического полиморфизма, значит использовать dynamic_cast - вполне нормально. И более того, это безопасно, т.к. можно проверить результат операции.

                                          Угу. Всё верно.
                                          Но-вопервых, виртуальные функции, не такие уж тяжолые. Зато дают плюсы.
                                          А во-вторых, динамическое приведение, это дополнительная примочка. Если она действительно нагружает ресурсы, то от неё придётся избавлятся. Ежели она быстро проверяет таблицу виртуальных членов, не отжирая ресурсы, то я бы занялся чем-то более интересным, чем попытки избавится от лишних кастов.
                                            Цитата Eric-S @
                                            Серьёзной просадкой производительности.

                                            А их не смущает что динамический полиморфизм как раз и есть причина серьезной просадки производительности? :D Ну это как знаешь, представь что Статический Полиморфизм - это гоночный болид, в него влазит 1 человек и все - едет быстро, весит мало. А Динамический Полиморфизм - это эдакий белаз - хранит кучу инфы, предоставляет гибкую работу с типами, но едет медлено, и весит много - жертвуешь серьезной просадкой производительности. Но зато в замен имеешь кучу бонусов и возможностей, которые без использования - не получил бы, так вот static_cast и dynamic_cast - это эдакие гаечные ключи к этим разным автомобилям. Один прямоугольный, а второй овальный. Так вот один ключ для гоночного болида - ты используешь не смущаясь, чтоб подтянуть гайки на колесах :D А вот второй ключ для белаза - ты использовать категорически отказываешься, аргументируя это тем, что ключ большой, и весит много. Лучше я буду затягивать гайки руками.
                                            Сообщение отредактировано: KILLER -
                                              Цитата KILLER @
                                              Это все понятно. Но ответь на вопрос, мне просто реально интересно, чем аргументируют?

                                              Кивают на снижение производительности, сравнение имён типов и прочую лабудень.

                                              Мне вот интересно, ты специально, перефразировал мой вопрос?

                                              Я ведь почему создал тему? Полистал инет, почитал всякие фразочки на счёт dynamic_cast и захотел понять, столь ли страшен зверь, как его малюют?

                                              А ты просишь пересказывать те самые байки, на которых я хочу поставить крест.

                                              Добавлено
                                              Цитата KILLER @
                                              А вот второй ключ для белаза - ты использовать категорически отказываешься, аргументируя это тем, что ключ большой, и весит много. Лучше я буду затягивать гайки руками.

                                              Я не отказываюсь. Боле того, я его активно применяю.
                                              Но вот некоторые говорят, что это черезвычайно плохо и приведёт к апокалипсису. А я не решаюсь выбросить удобный инструмент. Что-то в апакалипсис не верится.
                                                Цитата Eric-S @
                                                Но-вопервых, виртуальные функции, не такие уж тяжолые. Зато дают плюсы.

                                                По сравнению с обычными функциями - они тяжелые. Причем на столько же на сколько dynamic_cast тяжелее static_cast. Понимаешь если смотреть на это все абстрактно - то dynamic_cast он тоже не тяжелый, ну подумаешь указатель там где то итерируется, сравнивая что то с чем то, но это ведь пустяк - если смотреть на это абстрактно, не сравнивая ни с чем, а хотя бы с возможностями современных компьютеров. А если начинать сравнивать со статикой - то динамика, как ни крути выходит более тяжелой.

                                                Цитата Eric-S @
                                                А во-вторых, динамическое приведение, это дополнительная примочка.

                                                Это не дополнительная примочка, это неотъемлимый инструмент динамического полиморфизма, ровно такой же как чисто виртуальные методы, виртуальное наследование и другое.

                                                Цитата Eric-S @
                                                Если она действительно нагружает ресурсы, то от неё придётся избавлятся.

                                                Ты не понял - у тебя не dynamic_cast нагружает ресурсы, а использование Динамического полиморфизма в принципе. Потому как это жрет больше памяти, потому что хранит информацию о типах, и работает медлено, потому что в куче и там как ни крути выполняются какие то операции, возможно иногда даже не тривиальные. Поэтому лично я считаю - что тебе нужно избавляться либо от динамического полиморфизма и отключать RTTI, а взамен придумывать что то, что тебе нужно на шаблонах. Так ты здорово выиграешь на скорости и памяти. А уж если ты используешь Динамический полиморфизм, то и нет никакого смысла отказываться от dynamic_cast, главное, что бы ты не просел в разы по скорости, нужно выносить его из всяких критичных мест - например циклы, лучше тогда сделать такую операцию до цикла(это если ты совсем уже параноик :D )

                                                Добавлено
                                                Цитата Eric-S @
                                                Кивают на снижение производительности, сравнение имён типов и прочую лабудень.

                                                А от нее никуда не уйти - даже если ты не используешь dynamic_cast.

                                                Цитата Eric-S @
                                                Мне вот интересно, ты специально, перефразировал мой вопрос?

                                                Эээм. Где? Вообще у меня не было цели перефразировать твой вопрос или подменять понятия. Возможно я где то ошибся?

                                                Цитата Eric-S @
                                                Я ведь почему создал тему? Полистал инет, почитал всякие фразочки на счёт dynamic_cast и захотел понять, столь ли страшен зверь, как его малюют?

                                                Ну я тебе все написал. Делай выводы - это не отдельный зверь, не примочка, не бонус - это неотъемлимый инструмент динамического полиморфизма. Главное применять его нужно с умом, а не лепить где попало - к месту и не к месту.

                                                Цитата Eric-S @
                                                А ты просишь пересказывать те самые байки, на которых я хочу поставить крест.

                                                Эээм? Какие байки? :blush:

                                                Цитата Eric-S @
                                                Но вот некоторые говорят, что это черезвычайно плохо и приведёт к апокалипсису. А я не решаюсь выбросить удобный инструмент. Что-то в апакалипсис не верится.

                                                Это хорошо, что не верится. Потому что скорее всего эти люди, которые кричат что де все проседает, не до конца понимают механизм работы динамического полиморфизма в целом. Да, можно отказаться от dynamic_cast, может где то, что то, ты даже и выиграешь. Но зато ты будешь без него изгаляться и плодить всякие левые ненужные сущности, применять durty хаки, и плодить скрытые ошибки, отказываться от клевых плюшек, и писать велосипеды, закладывая грабли как раз перед ногами.
                                                  Цитата KILLER @
                                                  Поэтому лично я считаю - что тебе нужно избавляться либо от динамического полиморфизма и отключать RTTI, а взамен придумывать что то, что тебе нужно на шаблонах. Так ты здорово выиграешь на скорости и памяти. А уж если ты используешь Динамический полиморфизм, то и нет никакого смысла отказываться от dynamic_cast

                                                  Вот! Это уже понятный ответ.
                                                  А поскольку от динамического полиморфизма избавится не представляется возможным, то значит оставляю dynamic_cast и прекращаю страдать.

                                                  Вынести из циклов... Это логично. Вообще, попробую конечно вынести, там, где это легко получится. А остальное, пусть будет.

                                                  Кстати, вот нашёлся старый тест https://tinodidriksen.com/2010/04/cpp-dynam...st-performance/
                                                    Каждый вызов виртуального метода - сродни вызову dynamic_cast по сути. Так как при вызове вирт. метода, происходит такой же обход VMT, как и при вызове dynamic_cast, ну возможно он немножко шустрее чем каст. Но производительность от этого все равно не вырастает.

                                                    Добавлено
                                                    Eric-S, а можешь плз кинуть ссылку, где пишут что dynamic_cast использовать не по феншую, и вообще все будет жутко тупить и чехлить. Мне просто интересно почитать. Если конечно ты это на веб сайте где то прочел, а не в реале слышал.
                                                      Цитата KILLER @
                                                      Эээм. Где? Вообще у меня не было цели перефразировать твой вопрос или подменять понятия. Возможно я где то ошибся?

                                                      Самое первое сообщение. Ну... Может этот вопрос немного не явный.Там их достаточно много.
                                                      "насколько тормозноый", "можно ли заменить static_cast или хаками?"
                                                      Впрочем, ты уже на все эти вопросы ответил. И разогнал мои сомнения.

                                                      Добавлено
                                                      Цитата KILLER @
                                                      а можешь плз кинуть ссылку, где пишут что dynamic_cast использовать не по феншую, и вообще все будет жутко тупить и чехлить.

                                                      Ну вот например в комментах https://habrahabr.ru/post/257071/
                                                      А вообще, несколько раз попадалось, в том числе на хабре.
                                                        Цитата Eric-S @
                                                        Кстати, вот нашёлся старый тест https://tinodidriksen.com/2010/04/cpp-dynam...st-performance/

                                                        Ну вот и ответ тебе. В разы медленее, в такие же разы динамический полиморфизм медленее статического ))
                                                          Цитата KILLER @
                                                          Каждый вызов виртуального метода - сродни вызову dynamic_cast по сути. Так как при вызове вирт. метода, происходит такой же обход VMT, как и при вызове dynamic_cast, ну возможно он немножко шустрее чем каст. Но производительность от этого все равно не вырастает.

                                                          Если так, то это не очень страшно. Конечно, будет промах кэша процессора. Ну да ладно, не критично, можно пережить.
                                                            Цитата Eric-S @
                                                            Ну вот например в комментах https://habrahabr.ru/post/257071/

                                                            Хм. Как по мне, так участник под ником dyadyaSerezha вполне доступно распедаливает оппоненту где он не прав. Плюс еще что то там даже переписал, замерил и получил результаты.
                                                              Цитата KILLER @
                                                              Ну вот и ответ тебе. В разы медленее, в такие же разы динамический полиморфизм медленее статического ))

                                                              Тест... По-моему немного надуманный и не совсем адекватный. Я сильно засомневался в его корректности. Но может я ошибаюсь или чего-то не понимаю. Поэтому не стал судить и учитывать.
                                                                Ну просто как бы вызывать в цикле 10 миллионов раз виртуальный метод, или юзать туда dynamic_cast - так а чего он ожидал то? :D Чем то приходится платить за удобство. Пусть переписывает цикл, выносит все виртуальные вызовы за цикл или пусть архитектуру переделывает. Может у него в архитектуре косяк, раз он собрался вызывать 10 миллионов раз динамик каст. Я конечно понимаю что это всего лишь синтетический тест на замерку времени работы той или иной плюшки. Но ведь тогда это остается в рамках теста! И к реальному миру имеет слабое отношение. Разве что с академической стороны вопроса.

                                                                Добавлено
                                                                Цитата Eric-S @
                                                                Тест... По-моему немного надуманный и не совсем адекватный. Я сильно засомневался в его корректности. Но может я ошибаюсь или чего-то не понимаю. Поэтому не стал судить и учитывать.

                                                                Я если честно - положа руку на сердце, признаюсь тебе - что статью я совсем не читал. Прочитал заголовок, и первые два предложения. И тут же переместился к коментариям. Поэтому не могу судить на сколько адекватный там тест, но судя по коментам - ты прав. Он не совсем адекватный.
                                                                  Цитата KILLER @
                                                                  Как по мне, так участник под ником dyadyaSerezha вполне доступно распедаливает оппоненту где он не прав. Плюс еще что то там даже переписал, замерил и получил результаты.

                                                                  Правота кого-то становится очевидной, если заранее известен ответ. Ну или несколько раз штудировать весь текст.
                                                                  Да, действительно, dyadyaSerezha адекватно и обосновано отвечает.
                                                                  И твоя позиция, очень схожа с его позицией.

                                                                  Но я, просто так, вставать на чью-то сторону, не захотел. Сначала я усомнился во всё написанном и пожелал более вразумительных пояснений.
                                                                    Цитата Eric-S @
                                                                    Но я, просто так, вставать на чью-то сторону, не захотел. Сначала я усомнился во всё написанном и пожелал более вразумительных пояснений.

                                                                    Ясненько. Ну и правильно сделал. Тема таки интересная все же. :)
                                                                      Цитата KILLER @
                                                                      И к реальному миру имеет слабое отношение. Разве что с академической стороны вопроса.

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

                                                                      Дёргать миллион раз? Ха-эм... У меня количество вызовов зависит от входных данных. Может быть ноль, а может и миллион. Но вероятно, будет не слишком много. Хотя виртуальные функции дёргаются очень злосно.

                                                                      Добавлено
                                                                      Цитата KILLER @
                                                                      Ясненько. Ну и правильно сделал. Тема таки интересная все же. :)

                                                                      А это у меня вылезает постоянно. Вот как заклинит на чём-то, как начну сомневаться.... Даже порой над очевидными вещами. Иногда вылезает профит, иногда нет. Но всегда интересно поковыряться.
                                                                        Цитата Eric-S @
                                                                        Ха-эм... У меня количество вызовов зависит от входных данных. Может быть ноль, а может и миллион.

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

                                                                        Добавлено
                                                                        Если писать какой то виндовз примитивный калькулятор, то там хоть как не извращайся, все равно на производительности это не особо скажется. Ну если все извращения в рамках разумного конечно .
                                                                        Сообщение отредактировано: KILLER -
                                                                          Цитата Eric-S @
                                                                          Хэх. На самом деле мне понятны лишь отдельные куски. А вот общего понимания не возникло.
                                                                          Что там вообще происходит?
                                                                          Зачем это нужно?
                                                                          Почему именно так, а не иначе?

                                                                          Ну вот например со списками типов, я кое-как разобрался, да ито с третьего захода, после того как колупал реализацию типа variant.
                                                                          Так и не надо колупать-то. Реализация там приведена, чтобы её можно было использовать. Чтобы понять, как использовать, достаточно прочитать пример использования.
                                                                          Там всё просто: объявляем динамически связываемые параметры (списки типов полиморфных иерархий); определяем параметры и тип их связывания (динамический или статический); объявляем сигнатуру мультиметода (список типов его параметров); определяем класс-контейнер с реализациями первичного мультиметода и всех его перекрытий (это которые apply). Макросом интегрируем visit-метод в классы, создаём диспетчер и всё – в нужных местах вызываем его и получаем управление в одном из apply.
                                                                          Вообще, с этим лучше туда.

                                                                          Добавлено
                                                                          Цитата Eric-S @
                                                                          IObject это множество классов описывающих объект.
                                                                          IProvider это другое множество классов выполняющих действие с объектом.

                                                                          Правда, один конкретный IProvider может выполнять действие лишь с одним лини нисколькими типами объектов. Так же для одного типа IObject подходит один или несколько провайдеров. Но всё равно, разнообразных сочетаний очень много.
                                                                          Это нестрашно. Относись к мультиметодам как перегрузке, фактически оно так и есть. Только перегрузка разрешается в run-time диспетчером, а не в compile-time компилятором. Ты ж когда используешь перегрузку, не особо задумываешься о том, что не каждая комбинация параметров имеет точно однозначное соответствие с прототипом одной из перегруженных функций.

                                                                          Добавлено
                                                                          Цитата Eric-S @
                                                                          Я ведь почему создал тему? Полистал инет, почитал всякие фразочки на счёт dynamic_cast и захотел понять, столь ли страшен зверь, как его малюют?
                                                                          Знаешь, те мультиметоды писаны под C++03. Я уже с полгода как в свободное время потихонечку портирую... э-э-э, начиняю их новостандартными фичами, и они становятся куда удобнее. Только делается это довольно медленно, т.к. меня ничто не гонит, а свободного времени не так много (честно? я лучше к прохождению Wolfenstein 2 на Mein leben подготовлюсь, благо Episode 1 на ютуб уже выложил, и отдохну от Freescale-овых приколов, чем буду отдыхать в мультиметодах с приколами от Visual Studio), и в частности я решил сделать сразу две реализации: ту самую, и ещё без интрузивного visitor-а.
                                                                          Естественно, вторая реализация далеко не так производительна. Если первая стоит два всего лишь полиморфных вызова на каждый динамически связываемый параметр, то вторая выполняет бинарный поиск в сортированном контейнере на каждый параметр. Там, правда, нет dynamic_cast<>, но много typeid().before(). И к моему удивлению скорость упала далеко не так сильно, как я думал: после всех оптимизаций всего-то раза в три-пять.
                                                                          Так что слушать пессимистов полезно, но надо понимать, всё познаётся в сравнении. Та же задача, решаемая велосипедом вместо стандартных средств, будет менее надёжной и более дорогой в создании, и вряд ли окупится в одноразовом применении. Но после доработки напильником в плане оптимизации ресурсоёмких операций стандартные средства зачастую оказываются не так плохи.

                                                                          Добавлено
                                                                          Цитата KILLER @
                                                                          Каждый вызов виртуального метода - сродни вызову dynamic_cast по сути. Так как при вызове вирт. метода, происходит такой же обход VMT, как и при вызове dynamic_cast, ну возможно он немножко шустрее чем каст.
                                                                          Это ты сейчас описал не полиморфный вызов, а динамический. В Плюсах такого нет, но есть в ОбджектПаскале, а также во всякоразных фреймворковых библиотеках, типа Qt. Виртуальный вызов по факту просто косвенный: в конструкторе в экземпляр пишется указатель на vmt, в vmt при компиляции/сборке записаны указатели на методы, так что в рантайм происходит вычитка поинтера на vmt, его индексация в массиве поинтеров на методы, вычитка целевого поинтера и вызов по нему (с возможным кастом this, для чего компилер делает ещё един маленький thunk, но уже с прямым вызовом). По сравнению с dynamic_cast<> тут нет ничего ресурсоёмкого.
                                                                          Сообщение отредактировано: Qraizer -
                                                                            Цитата Qraizer @
                                                                            Это ты сейчас описал не полиморфный вызов, а динамический. В Плюсах такого нет, но есть в ОбджектПаскале, а также во всякоразных фреймворковых библиотеках, типа Qt. Виртуальный вызов по факту просто косвенный: в конструкторе в экземпляр пишется указатель на vmt, в vmt при компиляции/сборке записаны указатели на методы, так что в рантайм происходит вычитка поинтера на vmt, его индексация в массиве поинтеров на методы, вычитка целевого поинтера и вызов по нему (с возможным кастом this, для чего компилер делает ещё един маленький thunk, но уже с прямым вызовом). По сравнению с dynamic_cast<> тут нет ничего ресурсоёмкого.

                                                                            Да, согласен, я тут немного наврал ты прав. Там же указатель как раз и перескакивает на нужныые методы по таблице во время присваивания, когда не нужно кастить или когда явно скастовали.
                                                                              Цитата KILLER @
                                                                              Может тогда стоит пересмотреть архитектуру и использовать массив каких нибудь легковесных объектов, взамен вызова виртуального(ых) метода(ов). Хотя опять же, это имеет смысл тогда, когда тебе действительно это мешает, и серьезно искажает ожидаемый результат.

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

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

                                                                              Преждевременно не хочу оптимизировать, многое может поменятся. Пока делаю только самое базовое, что точно останется.

                                                                              Добавлено
                                                                              Цитата KILLER @
                                                                              использовать массив каких нибудь легковесных объектов, взамен вызова виртуального(ых) метода(ов).

                                                                              Не знаю... Там достаточно сложно и разветвлено. Как-то проще с виртуальными методами. Например довольно часто надо класть объекты в коллекции, чтобы единообразно их хранить и обрабатывать. Причём объекты разные, с разными свойствами.
                                                                              В некоторых, отдельных случаях, что-то можно оптимизировать. Но в остальных случаях, надо долго думать и не факт, что чего-то придумается.

                                                                              Добавлено
                                                                              Цитата Qraizer @
                                                                              Реализация там приведена, чтобы её можно было использовать.

                                                                              В чистом виде пока не заинтересовала. Реализацию изучал, чтобы подсмотреть чего-нибудь интересненькое, прокачать свой левел.

                                                                              Добавлено
                                                                              Цитата Qraizer @
                                                                              Так что слушать пессимистов полезно, но надо понимать, всё познаётся в сравнении. Та же задача, решаемая велосипедом вместо стандартных средств, будет менее надёжной и более дорогой в создании, и вряд ли окупится в одноразовом применении. Но после доработки напильником в плане оптимизации ресурсоёмких операций стандартные средства зачастую оказываются не так плохи.

                                                                              Ну да. Это верно. Только вот надо эти стандартные средства правильно использовать.
                                                                              Я как-то одну свою прогу ускорил, просто навтыкав в неё std::move() и string_veiw... Субьъективно раз в десять. А по приблизительным замерам, где-то на 30%. Ну много у меня там std::string туда-сюда копировались!
                                                                                Цитата KILLER @
                                                                                причем строится она конкретно в момент создания объекта динамически полиморфного типа, т.е. в рантайме, да еще и в куче
                                                                                Не совсем так. Точнее, совсем не так.
                                                                                Таблица виртуальных методов фиксирована для всего класса, поэтому нет необходимости строить её заново при создании объектов Все таблицы виртуальных методов компилятор создаёт статически на этапе компиляции. Тогда же строит дерево наследования, необходимое для работы dynamic_cast.
                                                                                При создании объекта в него просто добавляется ссылка на соответствующую таблицу виртуальных методов.

                                                                                Вызов такого виртуального метода стоит достаточно мало. Если вызовы невиртуальных методов выполняются простой командой вызова подпрограммы, то для виртуальных сперва из объекта извлекается адрес VMT, а потом происходит вызов функции по заданному смещению. То есть прямой вызов функции заменяется на одну выборку из памяти и косвенный вызов.

                                                                                dinamic_cast тоже не всегда сильно тормозит. В случае простой иерархии, когда граф наследования имеет вид дерева, dynamic_cast всего лишь проверяет находится ли таблица переданного ему указателя в поддереве наследников затребованного класса (или является корнем). Это делает короткий цикл из трёх команд. Плюс пара команд, нужных в случаях нулевого указателя и невозможности приведения. В таком случае время его работы пропорционально высоте дерева наследования от затребованного класса до его "самой базовой" базы.

                                                                                Реально тормозить он начинает в случае множественного наследования при попытке скастить указатель в иерархию другого базового класса. Поскольку в этом случае просто пробежаться по дереву наследования не получается, и приходится производить полноценный поиск.
                                                                                  Цитата amk @
                                                                                  Реально тормозить он начинает в случае множественного наследования при попытке скастить указатель в иерархию другого базового класса.

                                                                                  Вот мне стало интересно забыл уже про такие проблемы ;)
                                                                                  Реально тормозит это сколько? ( секунда, минута, год или счет на микро если не нано?)
                                                                                  и в сравнении с чем, когда у тебя в апликации есть скажем сокет до двух секунд,
                                                                                  то это фигня, случае множественного наследования если наследовать от 10классов,
                                                                                  то наверно затормозит, Я не слышал чтоб бывало больше двух, ну и меня еще в 96году
                                                                                  научили , там где критична скорость не используй VF, хорошая темя для вопроса на интервью ,
                                                                                  проверить понимает чел тему или нет, для реальной задачи похоже на холивар,
                                                                                  IMHO.
                                                                                    Я всё что нужно для подсчёта написал. при восходящем приведении сканирование идёт по прямому пути. Нарисуй граф наследования и посчитай полное число ветвей от него к переданному указателю. Получишь число переходов.
                                                                                    Сообщение отредактировано: amk -
                                                                                      Как уже писал, всё же решил попробовать избавиться от приведения, чисто ради сравнения результата.
                                                                                      Так вот, результат.... Я разницы не заметил.
                                                                                      Скорость такая же... Если где-то увеличилась, то незначительно. А ведь кроме dynamic_cast, я раздухарившись, выпилил shared_ptr из той части кода, использовав простые указатели. Возможно ннебольшое ускорение.
                                                                                      Объём... Я предполагал уменьшение, за счёт удаления функций-обёрток приведения. Но были добавлены шаблонные классы, которые исполнили роль промежуточного виртуального интерфейса. Видимо из-за них, бинарный код даже немного вырос.
                                                                                      Но все результаты, можно считать статестической погрешностью. Поскольку, часть архитектуры пришлось перебирать и переделывать.
                                                                                      А вот взрывного улучшения или ухудшения мною вообще не замечены. Разве что исходный код, стал немного строже в плане типов и запутаннее в плане подключения заголовочных файлов.
                                                                                        Цитата Eric-S @
                                                                                        Разве что исходный код, стал немного строже в плане типов
                                                                                        Вот это правильно.
                                                                                        Цитата Eric-S @
                                                                                        и запутаннее в плане подключения заголовочных файлов.
                                                                                        А вот это скорее из-за того, что по живому резал.
                                                                                          Цитата amk @
                                                                                          Цитата Eric-S @
                                                                                          и запутаннее в плане подключения заголовочных файлов.
                                                                                          А вот это скорее из-за того, что по живому резал.

                                                                                          Там обратное связывание.

                                                                                          Группа файлов, в которых описываются классы "*object.h", требуют подключение заголовочного файла "manager.h".
                                                                                          Он же в свою очередь требует подключение шаблона "iprovider.h", а затем всех "*object.h".

                                                                                          И таких обратных зависимостей, несколько штук.

                                                                                          Разрулил это предобъявлением некоторых классов и подключением хидеров полных объявлений, в *.cpp файлы.

                                                                                          Резал конечно по живому, но как не кручу, иных вариантов не нахожу. Собственно в начальном варианте приведение использовал как раз для снижения обратной связнности.
                                                                                            Цитата amk @
                                                                                            Не совсем так. Точнее, совсем не так.
                                                                                            ...
                                                                                            В случае простой иерархии, когда граф наследования имеет вид дерева, dynamic_cast всего лишь проверяет находится ли таблица переданного ему указателя в поддереве наследников затребованного класса (или является корнем)
                                                                                            Цитата amk @
                                                                                            при восходящем приведении сканирование идёт по прямому пути. Нарисуй граф наследования и посчитай полное число ветвей от него к переданному указателю. Получишь число переходов

                                                                                            Как ты говоришь - "Не совсем так. Точнее, совсем не так". ;)
                                                                                            Для реализации dynamic_cast (и прочих задач rtti) достаточно и практически удобно\целесообразно хранить только "обратную" ссылку наследника на его непосредственного предка (или нескольких предков в случае множественного наследования). Хранить же "прямые" ссылки от предка на всех его возможных наследников (коих может быть туева хуча в случае базовых классов библиотек типа MFC, VCL и т.п.) практически нецелесообразно и нереально.
                                                                                            Поэтому dynamic_cast независимо от вида приведения типа ("восходящее" от наследника к предку, или "нисходящее" от предка к наследнику) начинает проверку от реального (а не номинального) типа кастуемого объекта и далее идет вверх по иерархии (перебирая предков) пока не встретит требуемый тип или не дойдет до корневого типа иерархии.
                                                                                            Поэтому в примере #1, когда dynamic_cast выполняется не "от балды", а в соответствии с доп.идентификатором класса ptr->Category (или же по typeid), производится всего лишь одна проверка - на соответствие реального типа объекта ptr* (его поля VMT) требуемому типу Beta (указателю на VMT этого класса) независимо от того, является ли Beta прямым наследником номинального типа Alpha или же его пра-пра-внуком в десятом поколении. Отсюда и вывод
                                                                                            Цитата Eric-S @
                                                                                            Так вот, результат.... Я разницы не заметил.
                                                                                              Ну собственно я про это и написал. Как ещё проверить находится ли один узел в поддереве другого. Единственный эффективный способ спуститься по дереву иерархии от наследника к предку. Мы либо наткнёмся на переданный класс и выполним приведение, либо дойдём до конца иерархии, так и не встретив переданного класса, и вернём ноль, в знак того, что приведение невозможно. Это при одиночном наследовании.
                                                                                              Можно развернуть цикл спуска в точке приведения, проверка будет быстрее. Кроме того, так можно прервать проверки до достижения самого корневого класса (переданный в dynamic_cast указатель сам может быть производным, и его базовые классы проверять незачем).
                                                                                              При множественном наследовании при скольжении по дереву возможно несколько продолжений. Однако только одно может вести к классу передаваемого указателя. В этом случае развёртывание цикла даёт даже больший эффект, так как не приходится перебирать варианты продолжений.
                                                                                                По-моему, вы упускаете из виду такой вариант, или я вас не так понял:
                                                                                                ExpandedWrap disabled
                                                                                                  class IInterface1 { /* ... */ };
                                                                                                  class IInterface2 { /* ... */ };
                                                                                                   
                                                                                                  class MyClass: public IInterface1, public IInterface2 { /* ... */ };
                                                                                                   
                                                                                                  void foo(IInterface1* pInt1)
                                                                                                  {
                                                                                                    /* ... */
                                                                                                   
                                                                                                    IInterface2 pInt2 = dynamic_cast<IInterface2*>(pInt1);
                                                                                                   
                                                                                                    if (pInt2 != 0)
                                                                                                    /* ... */
                                                                                                  }
                                                                                                Перекрёстное приведение – часто встречающаяся фича. Класс может реализовывать несколько интерфейсов, и нередко встаёт задача проверки того, поддерживает ли он тот или иной интерфейс, и делается это перекрёстным приведением. Причём тут ещё простой пример.

                                                                                                P.S. Говоря об обходе графа, я не преувеличивал.
                                                                                                Сообщение отредактировано: Qraizer -
                                                                                                  Цитата Qraizer @
                                                                                                  Перекрёстное приведение – часто встречающаяся фича.
                                                                                                  Вот тут согласен. В этом случае выглядит практически нереальным во время компиляции угадать цепочку приведения. Значит остаётся только бродить по графу разыскивая нужный маршрут. И это достаточно медленный процесс, особенно в случае неудачи.
                                                                                                    Цитата Qraizer @
                                                                                                    По-моему, вы упускаете из виду такой вариант, или я вас не так понял:

                                                                                                    Я лишь хотел уточнить, что несмотря на обобщенное название темы, автора (в первую очередь) интересует конкретная простая ситуация, когда за номинальным
                                                                                                    базовым типом ObjectBase* скрывается реальный объект типа XObject*, к которому и нужно выполнить dynamic_cast (см.#1 и пояснение в #8). Поэтому прежде чем разглагольствовать на общие темы (на 4 страницах), следовало бы пояснить, что в данном конкретном случае dynamic_cast выполняется максимально быстро (за одно сравнение typeid реального типа с требуемым).
                                                                                                      Цитата leo @
                                                                                                      Я лишь хотел уточнить, что несмотря на обобщенное название темы, автора (в первую очередь) интересует конкретная простая ситуация, когда за номинальным
                                                                                                      базовым типом ObjectBase* скрывается реальный объект типа XObject*, к которому и нужно выполнить dynamic_cast (см.#1

                                                                                                      Да, действительно, меня интересовала конкретика. Но ради понимания общего механизма, на будущее, все остальные объяснения, тоже были интересны.
                                                                                                        Цитата leo @
                                                                                                        за одно сравнение typeid реального типа с требуемым
                                                                                                        Сравнений может быть и больше. Приведение нормально выполняется ещё и в случаях, когда реальный тип оказывается наследником требуемого. А это можно выяснить только пробежав по всей цепочке наследования от реального типа к требуемому.
                                                                                                          Цитата amk @
                                                                                                          Приведение нормально выполняется ещё и в случаях, когда реальный тип оказывается наследником требуемого.

                                                                                                          Это уже обобщение. Я говорю о конкретном случае (см.#1 и #8), когда dynamic_cust выполняется не "от балды", а только после предварительной проверки типа объекта по уникальному идентификатору типа Category (или по тому же tipeid). Если автор обобщит задачу и будет присваивать одно значение Category двум и более наследуемым классам, то да - проверка будет выполняться по цепочке "от реального типа к требуемому". А пока у него реальный тип = требуемому, проверка при dynamic_cust выполняется за одно сравнение.
                                                                                                            Цитата leo @
                                                                                                            Если автор обобщит задачу и будет присваивать одно значение Category двум и более наследуемым классам, то да - проверка будет выполняться по цепочке "от реального типа к требуемому". А пока у него реальный тип = требуемому,
                                                                                                            проверка при dynamic_cust выполняется за одно сравнение.

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

                                                                                                            ExpandedWrap disabled
                                                                                                              ]
                                                                                                              class ObjectBase abstract
                                                                                                              {}
                                                                                                               
                                                                                                              class XObject:
                                                                                                              public virtual ObjectBase
                                                                                                              {};
                                                                                                               
                                                                                                              class YObject:
                                                                                                              public virtual ObjectBase
                                                                                                              {};
                                                                                                               
                                                                                                               
                                                                                                              class XYObject final:
                                                                                                              public XObject,
                                                                                                              public YObject
                                                                                                              {
                                                                                                              };


                                                                                                            Причём объекты нужны и могут быть типов XObject, YObject или XYobject.

                                                                                                            В первом сообщении, я спрашивал лишь про один фрагмент. Но оно действительно для всех классов.
                                                                                                            Несколько функций работают только с XObject или YObject.
                                                                                                            Понятно и допустимо, что они будут работать с XYObject.
                                                                                                            Но моя первоначальная функция приведения, точнее проверки категорий, ломались на таком. Пришлось делать идентификаторы из битовых флагов.

                                                                                                            ExpandedWrap disabled
                                                                                                              enum class ObjectCategory
                                                                                                              {
                                                                                                              XObject = 0x0011,
                                                                                                              YObject = 0x0012,
                                                                                                              XYObject = 0x0013
                                                                                                              };


                                                                                                            Ну и проверка, соответственно уже проверяла маску, а не полное равенство.
                                                                                                            Ох и морока же. Была мысль заюзать std::bitset или switch. Но остановился на битовых флагах.

                                                                                                            И да в предыдущем варианте тестирования, кроме приведение типа объекта, к его реальному типу, делалось соответственно приведение к одному из базовых типов. Если реальный объект XYObject, а нужен только XObject, указатель же вообще ObjectBase...
                                                                                                            Но даже в этом случае, всё отрабатывало быстро и чётко.

                                                                                                            Как я понимаю, сначала dynamic_cast смотрел VMT и обнаружив там XYObject, спускался вниз по иерархии, проверяя либо XObject либо YObject.
                                                                                                              Дружище, на сколько я понял, был главный вопрос этот:
                                                                                                              Цитата Eric-S @
                                                                                                              но насколько это плохо?
                                                                                                              Можно "копья ломать" еще стопицот страниц. Домыслы, догадки. Но решение в каких-то числах, пусть в шкальных оценках, ИМХО, тебе дадут тесты. Сделай пару-тройку синтетических примеров с разветвленным множественным наследованием, замерь на таймере высокого разрешения - и ты получишь, как минимум, порядок в разности скоростей. Чесслово, рассуждений выше для тестов - выше крыши. А вот численный результат, уверен, был бы любопытен всем :)
                                                                                                                Цитата JoeUser @
                                                                                                                Но решение в каких-то числах, пусть в шкальных оценках, ИМХО, тебе дадут тесты.

                                                                                                                Пытался замерять такты, через clock().
                                                                                                                там значения плавали, для одного и того же кода. При этом два диапазона значений, для двух разных кодов, были довольно близкими. Я не смог однозначно сказать, что какой-то код быстрее. Субъективно же чуточку ускорилось.
                                                                                                                Следовало сразу мерить профайлером, в более суровых и чистых условиях, а теперь поздняк метаться. На данный момент у меня нет действющих dynamic_cast.
                                                                                                                  Цитата Eric-S @
                                                                                                                  Я не смог однозначно сказать, что какой-то код быстрее. Субъективно же чуточку ускорилось.

                                                                                                                  На большлом компе проверял? проверь на железке с памятью 2М, и скоростью
                                                                                                                  машины времен начала 90-х, хотя бы.Либо каждый замен времени умножай на 100 или 1000,
                                                                                                                  и сравни есть ли разница, это че совсем точно,но лучше чем никак.
                                                                                                                    Цитата settler @
                                                                                                                    Либо каждый замен времени умножай на 100 или 1000,
                                                                                                                    Лучше повторять тест. Подобрать такое количество повторов, чтобы вместе они занимали 1-10 секунд. Тогда по крайней мере паре-тройке цифр в результате можно будет доверять.

                                                                                                                    Цитата Eric-S @
                                                                                                                    там значения плавали, для одного и того же кода. При этом два диапазона значений, для двух разных кодов, были довольно близкими. Я не смог однозначно сказать, что какой-то код быстрее.
                                                                                                                    Значит можно считать, что притормаживание несущественно. В любом случае основное время у тебя занимает обработка после проверки, и на фоне этого времени небольшое притормаживание dynamic_cast будет незаметно.
                                                                                                                    0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                                                                                                    0 пользователей:


                                                                                                                    Рейтинг@Mail.ru
                                                                                                                    [ Script execution time: 0,1463 ]   [ 17 queries used ]   [ Generated: 19.04.24, 20:26 GMT ]