На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Qraizer, Hsilgos
Страницы: (4) [1] 2 3 ... Последняя » все  ( Перейти к последнему сообщению )  
> насколько тормозной 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 -
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0491 ]   [ 17 queries used ]   [ Generated: 23.04.24, 18:04 GMT ]