Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.137.180.32] |
|
Сообщ.
#1
,
|
|
|
Здравствуйте!
В попытке избежать сверхглубокого связывания и разростания кода, использовал полиморфизм. Кое где можно было бы сделать иначе, но по всяким соображениям, выбрал более прямой вариант. В целом, он не так уж плох, хотя есть идиологически бяки, о которых я знаю. Ну да это отговорки, а речь о другом. Надо привести указатель класса Alpha, к указателю класса Beta. // базовый 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 совсем не круто, но насколько это плохо? |
Сообщ.
#2
,
|
|
|
Цитата Eric-S @ Насколько он медленный? Я думаю тебе поможет дизассемблер, ссылка есть в объявлениях раздела. Сделай синтетический пример для динамического и статического кастов, и посмотри что генерируется. Для беглой оценки - сойдет. Если нужно более точно, то тогда чисто 10-15 минутные тесты с использованием таймера высокого разрешения. |
Сообщ.
#3
,
|
|
|
Цитата Eric-S @ static_cast<> нормально работает всегда, когда и dynamic_cast<>, при условии, что компилятору доступна архитектура иерархии. Т.е. просто на опережающих объявлениях, например, static_cast<> обломается, а dynamic_cast<> вывезет.Поскольку наследование virtual, то пришлось взять именно dynamic_cast. Без виратуального наследования, нормально работает static_cast. Единственное важное отличие заключается в том, что static_cast<> работает исключительно со статическим типом объекта, а значит динамический его тип ему не известен, поэтому в результате каста можешь получить UB, если в run-time окажется, что переданный объект нужного подобъекта не содержит. А вот dynamic_cast<> работает именно в run-time и ищет затребованный подобъект в переданном объекте, т.е. делает то же самое, но с проверкой успеха. Скорость зависит от величины иерархии. dynamic_cast<> может ходить не только вниз, но и вбок в случае множественного наследования. Фактически там осуществляется обход графа. Конечно, необязательно полный, но в случае фэйла он таковым получится. Ну и от качества реализации зависит, обычно компилятор никакого кода не генерирует, кроме вызова служебной библиотечной функции. Зато он генерирует сам граф, построенный на RTTI по каждому классу иерархии. Ты всегда можешь ограничиться static_cast<>, если имеешь гарантию того, что целевой класс точно имеется в исходном объекте. Например, в этом случае 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(); |
Сообщ.
#4
,
|
|
|
Цитата JoeUser @ Я думаю тебе поможет дизассемблер Он поможет изучить конкретную реализацию. Но... Гы-ха-эм... Со static_cast вообще не компилируется. Тут вопрос стоит в иной плоскости. Оставить ли мне dyanamic_cast или навернуть архитектуру до полного выноса мозга, чтоб избавиться от приведений? |
Сообщ.
#5
,
|
|
|
Цитата Eric-S @ Со static_cast вообще не компилируется. Покажи синтетический пример кода. |
Сообщ.
#6
,
|
|
|
Цитата Qraizer @ static_cast<> нормально работает всегда, когда и dynamic_cast<>, при условии, что компилятору доступа архитектура иерархии. Понятно. В моём случае, там ещё сложнее. На самом деле я пробовал static_pointer_cast, поскольку у меня указатели shared_ptr. Приведение выполнял в коде, где доступны полные описания иерархии классов. Но компилятор msvs 2017, заявил что не может делать статическое приведение при виртуальном наследовании. |
Сообщ.
#7
,
|
|
|
Цитата JoeUser @ Покажи синтетический пример кода. Ну вот например https://ideone.com/H30Gbv #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; } |
Сообщ.
#8
,
|
|
|
Цитата Qraizer @ Ты всегда можешь ограничиться static_cast<>, если имеешь гарантию того, что целевой класс точно имеется в исходном объекте. ... Но вот в твоём случае не факт. В моём случае, реальном, а не ситетическом, такие гарантии есть. См метод Category(). Каждый класс перегружает его, а объект возвращает уникальный идентификатор класса. Впрочем, даже это сделано для подстраховки. Сам контекст предполагает, что переданн заведомо коректный объект. Впрочем, если это не так, то это уже фатальная ошибка. Она должна вылезти в модульных тестах. Да, действительно, было бы хорошо использовать строгую типизацию, без всяких приведений. Но по сути, мне там нужен навороченный мультиметод. Я же попытался обойтись паттерном "visitor". Причём сделал общий интерфейс. Именно из-за интерфейса ломается типизация. |
Сообщ.
#9
,
|
|
|
Eric-S, есть тема. Возможно я еще не дорос, не осознал всю глубину глубин. Но смею предположить, что если отделить реализацию классов от умных указателей - то твоя композиция классов значительно упростится.
Твой пост навёл меня на гугление в лайт-стиле, и я нашел статью. На первый взгляд "обнять и плакать". "Улучшили в одном, усложнили в десяти". Это первое впечатление. Конечно же допускаю, что я не прав в квадрате. Но чуйка нашептывает ... "помни о Бритве Оккама". |
Сообщ.
#10
,
|
|
|
Цитата 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 @ Но чуйка нашептывает ... "помни о Бритве Оккама". Отрезать лишнее? Ну... Где-то конечно можно. А в целом, всё равно умные указатели очень нужны. Если их отрезать, точно придётся делать свой велосипед... Впрочем, он у меня где-то пылится, ндо найти... Но там всё тоже самое. Правда лунопарк без шлюх и блэкджека. |
Сообщ.
#11
,
|
|
|
Цитата Eric-S @ Хм... впрочем, возможно некоторые оптимизации размещения ему мешают. В ряде случаев действительно, не зная динамического типа, смещение до виртуальной базы не определить. Но компилятор msvs 2017, заявил что не может делать статическое приведение при виртуальном наследовании. Добавлено Цитата Eric-S @ Это не гарантия. Ты (ошибочно?) можешь назначить одинаковые категории разным классам. В моём случае, реальном, а не ситетическом, такие гарантии есть. См метод Category(). Добавлено Цитата Eric-S @ Приведение не обязательно "ломает типизацию". dynamic_cast<> вообще можно рассматривать как селектор интерфейсов при нескольких их реализованных в классе.Да, действительно, было бы хорошо использовать строгую типизацию, без всяких приведений. Но по сути, мне там нужен навороченный мультиметод. Я же попытался обойтись паттерном "visitor". Причём сделал общий интерфейс. Именно из-за интерфейса ломается типизация. Конкретно по мультиметодам можешь посмотреть мою тему по ним. Там очень высокая производительность, без dynamic_cast<> и тоже visitor. |
Сообщ.
#12
,
|
|
|
Цитата Qraizer @ Конкретно по мультиметодам можешь посмотреть мою тему по ним. Там очень высокая производительность, без dynamic_cast<> и тоже visitor. В который раз пытаюсь осилить ту реализацию Мультиметоды Очень интересно... И не всё понятно. Но это не совсем мой случай. Мне бы настройку мультиметодов упростить. Там как-то сложно. Вообще у меня, не совсем чистый мультиметод. А упрощённая вариация, где типы, более мене попарно связаны. Хотя. делается это через три виртуальных метода. У меня параметром виртуальной функции передаётся объект менеджера объектов, который либо создаёт новый объект, либо достаёт его из пула. Этот менеджер заменяет второй список параметров в мультиметоде. result_type XObject::action( const manager_type& manager ) override { // получить провайдер действия для объекта класса XObject IProvider* provider = manager.XObject(); // выполнить действие над объектом return provider->Action( this ); } Менеджер возвращает объект с интерфейсом IProvider. class IProvider { public: virtual result_type Action( ObjectBase* ptr ) = 0; }; Есть множество наследников ObjectBase, и для каждого есть несколько вариантов наследующих IProvider. Менеджер обобщает и пытается свести воедино. Но метод IProvider::Action() принимает только тип ObjectBase* Вот в этом самом месте ломается типизация. Впринципе, можно для каждого провайдера, сделать свой интерфейс, принимающий конкретный тип объекта. Но в этом случае, сложнее будет расширять менеджер объектов. Я собственно и поднял данную тему, чтобы понять, стоит ли мне заморочится с отдельными интерфейсами, или же можно оставить динамическое приведение? |
Сообщ.
#13
,
|
|
|
Цитата Eric-S @ А что там непонятно? Спросил бы.Но это не совсем мой случай. Мне бы настройку мультиметодов упростить. Там как-то сложно. Цитата Eric-S @ Что-то я тут мультиметода и не увидел даже. Как и необходимости в приведении типов вообще. Вообще у меня, не совсем чистый мультиметод. А упрощённая вариация, где типы, более мене попарно связаны. |
Сообщ.
#14
,
|
|
|
Цитата Qraizer @ Что-то я тут мультиметода и не увидел даже. Как и необходимости в приведении типов вообще. У меня мультиметод это Action. Если убрать все дополнения, то в чистом виде будут два параметра: result_type Action( IObject* o, IProvider* p ); IObject это множество классов описывающих объект. IProvider это другое множество классов выполняющих действие с объектом. Правда, один конкретный IProvider может выполнять действие лишь с одним лини нисколькими типами объектов. Так же для одного типа IObject подходит один или несколько провайдеров. Но всё равно, разнообразных сочетаний очень много. Но я не стал реальзовывать мультиметод в чистом и явном виде. IObject интерфейс класса с виртуальным методом Action, который принимает фабрику провайдеров. IProvider интерфейс класса с виртуальным методом Action, который принимает указатель на IObject. Вся эта фигня, маскирует мультиметод. Тем более он далеко не чистый. На счёт приведения, то оно нужно немного дальше. // интерфейс провайдера 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. Вообще, я вынужден признаться, что это пока не мой уровень. |
Сообщ.
#15
,
|
|
|
Цитата 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. |
Сообщ.
#16
,
|
|
|
Цитата KILLER @ Но у тебя я пока не вижу смысла делать виртуальное наследование. У тебя даже множественного наследования нет. Есть, множественное, в том числе ромбовидное. Но ты не мог его увидеть, поскольку я его не показывал. Добавлено Цитата KILLER @ В Debug сборке? Я бы не стал там юзать static_cast, т.к. у тебя полиморфный тип. Попробуй компильнуть в Release режиме с полной оптимизацией и чуть усложни архитектуру, и вылетишь с каким нибудь AV. Угу. Сейчас именно debug сборка. А что и где может вылетить? Я не понял, в чём проблема? Сейчас компильнул, всё вроде ок... Правда у меня боевой нагрузки нет, только модульные тесты. Добавлено Цитата KILLER @ Почему не круто? Идеальных архитектур не бывает, а dynamic_cast вполне себе безопасный вариант каста. Значит не так и плохо его юзать. Люди говорят, что не круто. А вот насколько это некруто, мне как раз, хотелось бы выяснить. |
Сообщ.
#17
,
|
|
|
Цитата 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, у тебя есть злоупотребление этими кастами - то значит что в твоей архитектуре что то не так. |
Сообщ.
#18
,
|
|
|
Цитата KILLER @ А static_cast - кастит/не кастит на этапе компиляции. Т.к. у тебя абстрактный класс, от которого наследуется дочерний, значит для них будет сгенерирована VMT. И я не думаю что static_cast вообще знает о ее существовании, т.к. он работает совершенно в другом измерении. Возможно в Debug сборке и при простой архитектуре, компилятор еще на этапе компиляции видит косяк, понимает что тут ошибка, и подменяет static_cast каким нибудь dynamic_cast, или просто срабатывает каст, как если бы у тебя не было виртуальных методов. Но мне кажется что при полной оптимизации и немного усложненной архитектуре, если не вылетит ошибка компиляции, то это еще не значит что static_cast будет работать всегда стабильно. Я ведь написал, что в случае с виртуальным наследованием, static_cast вообще не компилируется! Пришлось поставить именно dynamic_cast. Тут без вариантов. И как оно примерно работает, я представляю. Правда, вот некоторые люди утверждают, что microsoft и некоторые другие реализации, выполняют проверку типов, по их символьным именам. Добавлено Цитата KILLER @ А чем они аргументируют что это не круто? Серьёзной просадкой производительности. Добавлено Цитата KILLER @ Другое дело - если, как написал выше Qraizer, у тебя есть злоупотребление этими кастами - то значит что в твоей архитектуре что то не так. Я это знаю. Об этом оговорился сразу. Архитектура это отдельная проблема. Потихоньку решаю. |
Сообщ.
#19
,
|
|
|
Цитата Eric-S @ Я ведь написал, что в случае с виртуальным наследованием, static_cast вообще не компилируется! Могу лишь предположить, что при виртуальном наследовании создается какая нибудь виртуальная таблица классов, и компилятор на этапе компиляции понимает что static_cast тут применить в принципе не получится. А когда у тебя нет виртуального наследования, но есть виртуальные методы, и кастишь ты указатель на объект, то кастит этот static_cast так как умеет, т.е. твои виртуальные методы работать не будут. И получишь ты какой нибудь pure virtual call, но опять же повторюсь - это мое предположение. Я не уверен в том, что я прав на 100%. Цитата Eric-S @ И как оно примерно работает, я представляю. Это все понятно. Но ответь на вопрос, мне просто реально интересно, чем аргументируют? Вот вопрос: Цитата KILLER @ А чем они аргументируют что это не круто? |
Сообщ.
#20
,
|
|
|
Цитата KILLER @ Соответственно - если ты выбрал использование динамического полиморфизма, значит использовать dynamic_cast - вполне нормально. И более того, это безопасно, т.к. можно проверить результат операции. Угу. Всё верно. Но-вопервых, виртуальные функции, не такие уж тяжолые. Зато дают плюсы. А во-вторых, динамическое приведение, это дополнительная примочка. Если она действительно нагружает ресурсы, то от неё придётся избавлятся. Ежели она быстро проверяет таблицу виртуальных членов, не отжирая ресурсы, то я бы занялся чем-то более интересным, чем попытки избавится от лишних кастов. |
Сообщ.
#21
,
|
|
|
Цитата Eric-S @ Серьёзной просадкой производительности. А их не смущает что динамический полиморфизм как раз и есть причина серьезной просадки производительности? Ну это как знаешь, представь что Статический Полиморфизм - это гоночный болид, в него влазит 1 человек и все - едет быстро, весит мало. А Динамический Полиморфизм - это эдакий белаз - хранит кучу инфы, предоставляет гибкую работу с типами, но едет медлено, и весит много - жертвуешь серьезной просадкой производительности. Но зато в замен имеешь кучу бонусов и возможностей, которые без использования - не получил бы, так вот static_cast и dynamic_cast - это эдакие гаечные ключи к этим разным автомобилям. Один прямоугольный, а второй овальный. Так вот один ключ для гоночного болида - ты используешь не смущаясь, чтоб подтянуть гайки на колесах А вот второй ключ для белаза - ты использовать категорически отказываешься, аргументируя это тем, что ключ большой, и весит много. Лучше я буду затягивать гайки руками. |
Сообщ.
#22
,
|
|
|
Цитата KILLER @ Это все понятно. Но ответь на вопрос, мне просто реально интересно, чем аргументируют? Кивают на снижение производительности, сравнение имён типов и прочую лабудень. Мне вот интересно, ты специально, перефразировал мой вопрос? Я ведь почему создал тему? Полистал инет, почитал всякие фразочки на счёт dynamic_cast и захотел понять, столь ли страшен зверь, как его малюют? А ты просишь пересказывать те самые байки, на которых я хочу поставить крест. Добавлено Цитата KILLER @ А вот второй ключ для белаза - ты использовать категорически отказываешься, аргументируя это тем, что ключ большой, и весит много. Лучше я буду затягивать гайки руками. Я не отказываюсь. Боле того, я его активно применяю. Но вот некоторые говорят, что это черезвычайно плохо и приведёт к апокалипсису. А я не решаюсь выбросить удобный инструмент. Что-то в апакалипсис не верится. |
Сообщ.
#23
,
|
|
|
Цитата Eric-S @ Но-вопервых, виртуальные функции, не такие уж тяжолые. Зато дают плюсы. По сравнению с обычными функциями - они тяжелые. Причем на столько же на сколько dynamic_cast тяжелее static_cast. Понимаешь если смотреть на это все абстрактно - то dynamic_cast он тоже не тяжелый, ну подумаешь указатель там где то итерируется, сравнивая что то с чем то, но это ведь пустяк - если смотреть на это абстрактно, не сравнивая ни с чем, а хотя бы с возможностями современных компьютеров. А если начинать сравнивать со статикой - то динамика, как ни крути выходит более тяжелой. Цитата Eric-S @ А во-вторых, динамическое приведение, это дополнительная примочка. Это не дополнительная примочка, это неотъемлимый инструмент динамического полиморфизма, ровно такой же как чисто виртуальные методы, виртуальное наследование и другое. Цитата Eric-S @ Если она действительно нагружает ресурсы, то от неё придётся избавлятся. Ты не понял - у тебя не dynamic_cast нагружает ресурсы, а использование Динамического полиморфизма в принципе. Потому как это жрет больше памяти, потому что хранит информацию о типах, и работает медлено, потому что в куче и там как ни крути выполняются какие то операции, возможно иногда даже не тривиальные. Поэтому лично я считаю - что тебе нужно избавляться либо от динамического полиморфизма и отключать RTTI, а взамен придумывать что то, что тебе нужно на шаблонах. Так ты здорово выиграешь на скорости и памяти. А уж если ты используешь Динамический полиморфизм, то и нет никакого смысла отказываться от dynamic_cast, главное, что бы ты не просел в разы по скорости, нужно выносить его из всяких критичных мест - например циклы, лучше тогда сделать такую операцию до цикла(это если ты совсем уже параноик ) Добавлено Цитата Eric-S @ Кивают на снижение производительности, сравнение имён типов и прочую лабудень. А от нее никуда не уйти - даже если ты не используешь dynamic_cast. Цитата Eric-S @ Мне вот интересно, ты специально, перефразировал мой вопрос? Эээм. Где? Вообще у меня не было цели перефразировать твой вопрос или подменять понятия. Возможно я где то ошибся? Цитата Eric-S @ Я ведь почему создал тему? Полистал инет, почитал всякие фразочки на счёт dynamic_cast и захотел понять, столь ли страшен зверь, как его малюют? Ну я тебе все написал. Делай выводы - это не отдельный зверь, не примочка, не бонус - это неотъемлимый инструмент динамического полиморфизма. Главное применять его нужно с умом, а не лепить где попало - к месту и не к месту. Цитата Eric-S @ А ты просишь пересказывать те самые байки, на которых я хочу поставить крест. Эээм? Какие байки? Цитата Eric-S @ Но вот некоторые говорят, что это черезвычайно плохо и приведёт к апокалипсису. А я не решаюсь выбросить удобный инструмент. Что-то в апакалипсис не верится. Это хорошо, что не верится. Потому что скорее всего эти люди, которые кричат что де все проседает, не до конца понимают механизм работы динамического полиморфизма в целом. Да, можно отказаться от dynamic_cast, может где то, что то, ты даже и выиграешь. Но зато ты будешь без него изгаляться и плодить всякие левые ненужные сущности, применять durty хаки, и плодить скрытые ошибки, отказываться от клевых плюшек, и писать велосипеды, закладывая грабли как раз перед ногами. |
Сообщ.
#24
,
|
|
|
Цитата KILLER @ Поэтому лично я считаю - что тебе нужно избавляться либо от динамического полиморфизма и отключать RTTI, а взамен придумывать что то, что тебе нужно на шаблонах. Так ты здорово выиграешь на скорости и памяти. А уж если ты используешь Динамический полиморфизм, то и нет никакого смысла отказываться от dynamic_cast Вот! Это уже понятный ответ. А поскольку от динамического полиморфизма избавится не представляется возможным, то значит оставляю dynamic_cast и прекращаю страдать. Вынести из циклов... Это логично. Вообще, попробую конечно вынести, там, где это легко получится. А остальное, пусть будет. Кстати, вот нашёлся старый тест https://tinodidriksen.com/2010/04/cpp-dynam...st-performance/ |
Сообщ.
#25
,
|
|
|
Каждый вызов виртуального метода - сродни вызову dynamic_cast по сути. Так как при вызове вирт. метода, происходит такой же обход VMT, как и при вызове dynamic_cast, ну возможно он немножко шустрее чем каст. Но производительность от этого все равно не вырастает.
Добавлено Eric-S, а можешь плз кинуть ссылку, где пишут что dynamic_cast использовать не по феншую, и вообще все будет жутко тупить и чехлить. Мне просто интересно почитать. Если конечно ты это на веб сайте где то прочел, а не в реале слышал. |
Сообщ.
#26
,
|
|
|
Цитата KILLER @ Эээм. Где? Вообще у меня не было цели перефразировать твой вопрос или подменять понятия. Возможно я где то ошибся? Самое первое сообщение. Ну... Может этот вопрос немного не явный.Там их достаточно много. "насколько тормозноый", "можно ли заменить static_cast или хаками?" Впрочем, ты уже на все эти вопросы ответил. И разогнал мои сомнения. Добавлено Цитата KILLER @ а можешь плз кинуть ссылку, где пишут что dynamic_cast использовать не по феншую, и вообще все будет жутко тупить и чехлить. Ну вот например в комментах https://habrahabr.ru/post/257071/ А вообще, несколько раз попадалось, в том числе на хабре. |
Сообщ.
#27
,
|
|
|
Цитата Eric-S @ Кстати, вот нашёлся старый тест https://tinodidriksen.com/2010/04/cpp-dynam...st-performance/ Ну вот и ответ тебе. В разы медленее, в такие же разы динамический полиморфизм медленее статического )) |
Сообщ.
#28
,
|
|
|
Цитата KILLER @ Каждый вызов виртуального метода - сродни вызову dynamic_cast по сути. Так как при вызове вирт. метода, происходит такой же обход VMT, как и при вызове dynamic_cast, ну возможно он немножко шустрее чем каст. Но производительность от этого все равно не вырастает. Если так, то это не очень страшно. Конечно, будет промах кэша процессора. Ну да ладно, не критично, можно пережить. |
Сообщ.
#29
,
|
|
|
Хм. Как по мне, так участник под ником dyadyaSerezha вполне доступно распедаливает оппоненту где он не прав. Плюс еще что то там даже переписал, замерил и получил результаты. |
Сообщ.
#30
,
|
|
|
Цитата KILLER @ Ну вот и ответ тебе. В разы медленее, в такие же разы динамический полиморфизм медленее статического )) Тест... По-моему немного надуманный и не совсем адекватный. Я сильно засомневался в его корректности. Но может я ошибаюсь или чего-то не понимаю. Поэтому не стал судить и учитывать. |
Сообщ.
#31
,
|
|
|
Ну просто как бы вызывать в цикле 10 миллионов раз виртуальный метод, или юзать туда dynamic_cast - так а чего он ожидал то? Чем то приходится платить за удобство. Пусть переписывает цикл, выносит все виртуальные вызовы за цикл или пусть архитектуру переделывает. Может у него в архитектуре косяк, раз он собрался вызывать 10 миллионов раз динамик каст. Я конечно понимаю что это всего лишь синтетический тест на замерку времени работы той или иной плюшки. Но ведь тогда это остается в рамках теста! И к реальному миру имеет слабое отношение. Разве что с академической стороны вопроса.
Добавлено Цитата Eric-S @ Тест... По-моему немного надуманный и не совсем адекватный. Я сильно засомневался в его корректности. Но может я ошибаюсь или чего-то не понимаю. Поэтому не стал судить и учитывать. Я если честно - положа руку на сердце, признаюсь тебе - что статью я совсем не читал. Прочитал заголовок, и первые два предложения. И тут же переместился к коментариям. Поэтому не могу судить на сколько адекватный там тест, но судя по коментам - ты прав. Он не совсем адекватный. |
Сообщ.
#32
,
|
|
|
Цитата KILLER @ Как по мне, так участник под ником dyadyaSerezha вполне доступно распедаливает оппоненту где он не прав. Плюс еще что то там даже переписал, замерил и получил результаты. Правота кого-то становится очевидной, если заранее известен ответ. Ну или несколько раз штудировать весь текст. Да, действительно, dyadyaSerezha адекватно и обосновано отвечает. И твоя позиция, очень схожа с его позицией. Но я, просто так, вставать на чью-то сторону, не захотел. Сначала я усомнился во всё написанном и пожелал более вразумительных пояснений. |
Сообщ.
#33
,
|
|
|
Цитата Eric-S @ Но я, просто так, вставать на чью-то сторону, не захотел. Сначала я усомнился во всё написанном и пожелал более вразумительных пояснений. Ясненько. Ну и правильно сделал. Тема таки интересная все же. |
Сообщ.
#34
,
|
|
|
Цитата KILLER @ И к реальному миру имеет слабое отношение. Разве что с академической стороны вопроса. Даже ищё хуже. компиляторы чего-то заоптимизируют, процессоры где-то закэшируют... А в результате всякие такие тесты оказываются совсем далёкими от реальности. Дёргать миллион раз? Ха-эм... У меня количество вызовов зависит от входных данных. Может быть ноль, а может и миллион. Но вероятно, будет не слишком много. Хотя виртуальные функции дёргаются очень злосно. Добавлено Цитата KILLER @ Ясненько. Ну и правильно сделал. Тема таки интересная все же. А это у меня вылезает постоянно. Вот как заклинит на чём-то, как начну сомневаться.... Даже порой над очевидными вещами. Иногда вылезает профит, иногда нет. Но всегда интересно поковыряться. |
Сообщ.
#35
,
|
|
|
Цитата Eric-S @ Ха-эм... У меня количество вызовов зависит от входных данных. Может быть ноль, а может и миллион. Может тогда стоит пересмотреть архитектуру и использовать массив каких нибудь легковесных объектов, взамен вызова виртуального(ых) метода(ов). Хотя опять же, это имеет смысл тогда, когда тебе действительно это мешает, и серьезно искажает ожидаемый результат. Добавлено Если писать какой то виндовз примитивный калькулятор, то там хоть как не извращайся, все равно на производительности это не особо скажется. Ну если все извращения в рамках разумного конечно . |
Сообщ.
#36
,
|
|
|
Цитата Eric-S @ Так и не надо колупать-то. Реализация там приведена, чтобы её можно было использовать. Чтобы понять, как использовать, достаточно прочитать пример использования.Хэх. На самом деле мне понятны лишь отдельные куски. А вот общего понимания не возникло. Что там вообще происходит? Зачем это нужно? Почему именно так, а не иначе? Ну вот например со списками типов, я кое-как разобрался, да ито с третьего захода, после того как колупал реализацию типа variant. Там всё просто: объявляем динамически связываемые параметры (списки типов полиморфных иерархий); определяем параметры и тип их связывания (динамический или статический); объявляем сигнатуру мультиметода (список типов его параметров); определяем класс-контейнер с реализациями первичного мультиметода и всех его перекрытий (это которые apply). Макросом интегрируем visit-метод в классы, создаём диспетчер и всё – в нужных местах вызываем его и получаем управление в одном из apply. Вообще, с этим лучше туда. Добавлено Цитата Eric-S @ Это нестрашно. Относись к мультиметодам как перегрузке, фактически оно так и есть. Только перегрузка разрешается в run-time диспетчером, а не в compile-time компилятором. Ты ж когда используешь перегрузку, не особо задумываешься о том, что не каждая комбинация параметров имеет точно однозначное соответствие с прототипом одной из перегруженных функций. IObject это множество классов описывающих объект. IProvider это другое множество классов выполняющих действие с объектом. Правда, один конкретный IProvider может выполнять действие лишь с одним лини нисколькими типами объектов. Так же для одного типа IObject подходит один или несколько провайдеров. Но всё равно, разнообразных сочетаний очень много. Добавлено Цитата Eric-S @ Знаешь, те мультиметоды писаны под C++03. Я уже с полгода как в свободное время потихонечку Я ведь почему создал тему? Полистал инет, почитал всякие фразочки на счёт dynamic_cast и захотел понять, столь ли страшен зверь, как его малюют? Естественно, вторая реализация далеко не так производительна. Если первая стоит два всего лишь полиморфных вызова на каждый динамически связываемый параметр, то вторая выполняет бинарный поиск в сортированном контейнере на каждый параметр. Там, правда, нет dynamic_cast<>, но много typeid().before(). И к моему удивлению скорость упала далеко не так сильно, как я думал: после всех оптимизаций всего-то раза в три-пять. Так что слушать пессимистов полезно, но надо понимать, всё познаётся в сравнении. Та же задача, решаемая велосипедом вместо стандартных средств, будет менее надёжной и более дорогой в создании, и вряд ли окупится в одноразовом применении. Но после доработки напильником в плане оптимизации ресурсоёмких операций стандартные средства зачастую оказываются не так плохи. Добавлено Цитата KILLER @ Это ты сейчас описал не полиморфный вызов, а динамический. В Плюсах такого нет, но есть в ОбджектПаскале, а также во всякоразных фреймворковых библиотеках, типа Qt. Виртуальный вызов по факту просто косвенный: в конструкторе в экземпляр пишется указатель на vmt, в vmt при компиляции/сборке записаны указатели на методы, так что в рантайм происходит вычитка поинтера на vmt, его индексация в массиве поинтеров на методы, вычитка целевого поинтера и вызов по нему (с возможным кастом this, для чего компилер делает ещё един маленький thunk, но уже с прямым вызовом). По сравнению с dynamic_cast<> тут нет ничего ресурсоёмкого. Каждый вызов виртуального метода - сродни вызову dynamic_cast по сути. Так как при вызове вирт. метода, происходит такой же обход VMT, как и при вызове dynamic_cast, ну возможно он немножко шустрее чем каст. |
Сообщ.
#37
,
|
|
|
Цитата Qraizer @ Это ты сейчас описал не полиморфный вызов, а динамический. В Плюсах такого нет, но есть в ОбджектПаскале, а также во всякоразных фреймворковых библиотеках, типа Qt. Виртуальный вызов по факту просто косвенный: в конструкторе в экземпляр пишется указатель на vmt, в vmt при компиляции/сборке записаны указатели на методы, так что в рантайм происходит вычитка поинтера на vmt, его индексация в массиве поинтеров на методы, вычитка целевого поинтера и вызов по нему (с возможным кастом this, для чего компилер делает ещё един маленький thunk, но уже с прямым вызовом). По сравнению с dynamic_cast<> тут нет ничего ресурсоёмкого. Да, согласен, я тут немного наврал ты прав. Там же указатель как раз и перескакивает на нужныые методы по таблице во время присваивания, когда не нужно кастить или когда явно скастовали. |
Сообщ.
#38
,
|
|
|
Цитата KILLER @ Может тогда стоит пересмотреть архитектуру и использовать массив каких нибудь легковесных объектов, взамен вызова виртуального(ых) метода(ов). Хотя опять же, это имеет смысл тогда, когда тебе действительно это мешает, и серьезно искажает ожидаемый результат. Пока особо не мешает. Впрочем, часть операций я закэшировал. Например, у меня фабрика изначально создавала множество объектов. Причём одинаковых объектов. Там не только вызов виртуальных методов, но выделение и освобождение блоков памяти из кучи. Сейчас я закэшировал. Объект создаётся один раз и указатель сохраняется. В следующий раз, отдаётся именно указатель на старый объект. Виртуальный методо и память не выделяются. В планах, есть ещё более хитрое кэширование, с пулом других объектов. Но я там ещё не всё продумал и руки не дошли. Впрочем, этот пул оптимизирует только работы с памятью, а не виртуализации. Преждевременно не хочу оптимизировать, многое может поменятся. Пока делаю только самое базовое, что точно останется. Добавлено Цитата KILLER @ использовать массив каких нибудь легковесных объектов, взамен вызова виртуального(ых) метода(ов). Не знаю... Там достаточно сложно и разветвлено. Как-то проще с виртуальными методами. Например довольно часто надо класть объекты в коллекции, чтобы единообразно их хранить и обрабатывать. Причём объекты разные, с разными свойствами. В некоторых, отдельных случаях, что-то можно оптимизировать. Но в остальных случаях, надо долго думать и не факт, что чего-то придумается. Добавлено Цитата Qraizer @ Реализация там приведена, чтобы её можно было использовать. В чистом виде пока не заинтересовала. Реализацию изучал, чтобы подсмотреть чего-нибудь интересненькое, прокачать свой левел. Добавлено Цитата Qraizer @ Так что слушать пессимистов полезно, но надо понимать, всё познаётся в сравнении. Та же задача, решаемая велосипедом вместо стандартных средств, будет менее надёжной и более дорогой в создании, и вряд ли окупится в одноразовом применении. Но после доработки напильником в плане оптимизации ресурсоёмких операций стандартные средства зачастую оказываются не так плохи. Ну да. Это верно. Только вот надо эти стандартные средства правильно использовать. Я как-то одну свою прогу ускорил, просто навтыкав в неё std::move() и string_veiw... Субьъективно раз в десять. А по приблизительным замерам, где-то на 30%. Ну много у меня там std::string туда-сюда копировались! |
Сообщ.
#39
,
|
|
|
Цитата KILLER @ Не совсем так. Точнее, совсем не так. причем строится она конкретно в момент создания объекта динамически полиморфного типа, т.е. в рантайме, да еще и в куче Таблица виртуальных методов фиксирована для всего класса, поэтому нет необходимости строить её заново при создании объектов Все таблицы виртуальных методов компилятор создаёт статически на этапе компиляции. Тогда же строит дерево наследования, необходимое для работы dynamic_cast. При создании объекта в него просто добавляется ссылка на соответствующую таблицу виртуальных методов. Вызов такого виртуального метода стоит достаточно мало. Если вызовы невиртуальных методов выполняются простой командой вызова подпрограммы, то для виртуальных сперва из объекта извлекается адрес VMT, а потом происходит вызов функции по заданному смещению. То есть прямой вызов функции заменяется на одну выборку из памяти и косвенный вызов. dinamic_cast тоже не всегда сильно тормозит. В случае простой иерархии, когда граф наследования имеет вид дерева, dynamic_cast всего лишь проверяет находится ли таблица переданного ему указателя в поддереве наследников затребованного класса (или является корнем). Это делает короткий цикл из трёх команд. Плюс пара команд, нужных в случаях нулевого указателя и невозможности приведения. В таком случае время его работы пропорционально высоте дерева наследования от затребованного класса до его "самой базовой" базы. Реально тормозить он начинает в случае множественного наследования при попытке скастить указатель в иерархию другого базового класса. Поскольку в этом случае просто пробежаться по дереву наследования не получается, и приходится производить полноценный поиск. |
Сообщ.
#40
,
|
|
|
Цитата amk @ Реально тормозить он начинает в случае множественного наследования при попытке скастить указатель в иерархию другого базового класса. Вот мне стало интересно забыл уже про такие проблемы Реально тормозит это сколько? ( секунда, минута, год или счет на микро если не нано?) и в сравнении с чем, когда у тебя в апликации есть скажем сокет до двух секунд, то это фигня, случае множественного наследования если наследовать от 10классов, то наверно затормозит, Я не слышал чтоб бывало больше двух, ну и меня еще в 96году научили , там где критична скорость не используй VF, хорошая темя для вопроса на интервью , проверить понимает чел тему или нет, для реальной задачи похоже на холивар, IMHO. |
Сообщ.
#41
,
|
|
|
Я всё что нужно для подсчёта написал. при восходящем приведении сканирование идёт по прямому пути. Нарисуй граф наследования и посчитай полное число ветвей от него к переданному указателю. Получишь число переходов.
|
Сообщ.
#42
,
|
|
|
Как уже писал, всё же решил попробовать избавиться от приведения, чисто ради сравнения результата.
Так вот, результат.... Я разницы не заметил. Скорость такая же... Если где-то увеличилась, то незначительно. А ведь кроме dynamic_cast, я раздухарившись, выпилил shared_ptr из той части кода, использовав простые указатели. Возможно ннебольшое ускорение. Объём... Я предполагал уменьшение, за счёт удаления функций-обёрток приведения. Но были добавлены шаблонные классы, которые исполнили роль промежуточного виртуального интерфейса. Видимо из-за них, бинарный код даже немного вырос. Но все результаты, можно считать статестической погрешностью. Поскольку, часть архитектуры пришлось перебирать и переделывать. А вот взрывного улучшения или ухудшения мною вообще не замечены. Разве что исходный код, стал немного строже в плане типов и запутаннее в плане подключения заголовочных файлов. |
Сообщ.
#43
,
|
|
|
Цитата Eric-S @ Вот это правильно.Разве что исходный код, стал немного строже в плане типов Цитата Eric-S @ А вот это скорее из-за того, что по живому резал. и запутаннее в плане подключения заголовочных файлов. |
Сообщ.
#44
,
|
|
|
Цитата amk @ Цитата Eric-S @ А вот это скорее из-за того, что по живому резал.и запутаннее в плане подключения заголовочных файлов. Там обратное связывание. Группа файлов, в которых описываются классы "*object.h", требуют подключение заголовочного файла "manager.h". Он же в свою очередь требует подключение шаблона "iprovider.h", а затем всех "*object.h". И таких обратных зависимостей, несколько штук. Разрулил это предобъявлением некоторых классов и подключением хидеров полных объявлений, в *.cpp файлы. Резал конечно по живому, но как не кручу, иных вариантов не нахожу. Собственно в начальном варианте приведение использовал как раз для снижения обратной связнности. |
Сообщ.
#45
,
|
|
|
Цитата 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 @ Так вот, результат.... Я разницы не заметил. |
Сообщ.
#46
,
|
|
|
Ну собственно я про это и написал. Как ещё проверить находится ли один узел в поддереве другого. Единственный эффективный способ спуститься по дереву иерархии от наследника к предку. Мы либо наткнёмся на переданный класс и выполним приведение, либо дойдём до конца иерархии, так и не встретив переданного класса, и вернём ноль, в знак того, что приведение невозможно. Это при одиночном наследовании.
Можно развернуть цикл спуска в точке приведения, проверка будет быстрее. Кроме того, так можно прервать проверки до достижения самого корневого класса (переданный в dynamic_cast указатель сам может быть производным, и его базовые классы проверять незачем). При множественном наследовании при скольжении по дереву возможно несколько продолжений. Однако только одно может вести к классу передаваемого указателя. В этом случае развёртывание цикла даёт даже больший эффект, так как не приходится перебирать варианты продолжений. |
Сообщ.
#47
,
|
|
|
По-моему, вы упускаете из виду такой вариант, или я вас не так понял:
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. Говоря об обходе графа, я не преувеличивал. |
Сообщ.
#48
,
|
|
|
Цитата Qraizer @ Вот тут согласен. В этом случае выглядит практически нереальным во время компиляции угадать цепочку приведения. Значит остаётся только бродить по графу разыскивая нужный маршрут. И это достаточно медленный процесс, особенно в случае неудачи. Перекрёстное приведение – часто встречающаяся фича. |
Сообщ.
#49
,
|
|
|
Цитата Qraizer @ По-моему, вы упускаете из виду такой вариант, или я вас не так понял: Я лишь хотел уточнить, что несмотря на обобщенное название темы, автора (в первую очередь) интересует конкретная простая ситуация, когда за номинальным базовым типом ObjectBase* скрывается реальный объект типа XObject*, к которому и нужно выполнить dynamic_cast (см.#1 и пояснение в #8). Поэтому прежде чем разглагольствовать на общие темы (на 4 страницах), следовало бы пояснить, что в данном конкретном случае dynamic_cast выполняется максимально быстро (за одно сравнение typeid реального типа с требуемым). |
Сообщ.
#50
,
|
|
|
Цитата leo @ Я лишь хотел уточнить, что несмотря на обобщенное название темы, автора (в первую очередь) интересует конкретная простая ситуация, когда за номинальным базовым типом ObjectBase* скрывается реальный объект типа XObject*, к которому и нужно выполнить dynamic_cast (см.#1 Да, действительно, меня интересовала конкретика. Но ради понимания общего механизма, на будущее, все остальные объяснения, тоже были интересны. |
Сообщ.
#51
,
|
|
|
Цитата leo @ Сравнений может быть и больше. Приведение нормально выполняется ещё и в случаях, когда реальный тип оказывается наследником требуемого. А это можно выяснить только пробежав по всей цепочке наследования от реального типа к требуемому. за одно сравнение typeid реального типа с требуемым |
Сообщ.
#52
,
|
|
|
Цитата amk @ Приведение нормально выполняется ещё и в случаях, когда реальный тип оказывается наследником требуемого. Это уже обобщение. Я говорю о конкретном случае (см.#1 и #8), когда dynamic_cust выполняется не "от балды", а только после предварительной проверки типа объекта по уникальному идентификатору типа Category (или по тому же tipeid). Если автор обобщит задачу и будет присваивать одно значение Category двум и более наследуемым классам, то да - проверка будет выполняться по цепочке "от реального типа к требуемому". А пока у него реальный тип = требуемому, проверка при dynamic_cust выполняется за одно сравнение. |
Сообщ.
#53
,
|
|
|
Цитата leo @ Если автор обобщит задачу и будет присваивать одно значение Category двум и более наследуемым классам, то да - проверка будет выполняться по цепочке "от реального типа к требуемому". А пока у него реальный тип = требуемому, проверка при dynamic_cust выполняется за одно сравнение. У меня category выставляется полуавтоматически. Собственно я завёл её как раз ради такой или подобной идентификации. Например, чтобы из списка разных объектов, быстро выбрать объекты нужных категорий. Но в некоторых местах, получилось весело и неоднозначно. У меня есть ромбовидная иерархия... ] 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. Но моя первоначальная функция приведения, точнее проверки категорий, ломались на таком. Пришлось делать идентификаторы из битовых флагов. enum class ObjectCategory { XObject = 0x0011, YObject = 0x0012, XYObject = 0x0013 }; Ну и проверка, соответственно уже проверяла маску, а не полное равенство. Ох и морока же. Была мысль заюзать std::bitset или switch. Но остановился на битовых флагах. И да в предыдущем варианте тестирования, кроме приведение типа объекта, к его реальному типу, делалось соответственно приведение к одному из базовых типов. Если реальный объект XYObject, а нужен только XObject, указатель же вообще ObjectBase... Но даже в этом случае, всё отрабатывало быстро и чётко. Как я понимаю, сначала dynamic_cast смотрел VMT и обнаружив там XYObject, спускался вниз по иерархии, проверяя либо XObject либо YObject. |
Сообщ.
#54
,
|
|
|
Дружище, на сколько я понял, был главный вопрос этот:
Цитата Eric-S @ Можно "копья ломать" еще стопицот страниц. Домыслы, догадки. Но решение в каких-то числах, пусть в шкальных оценках, ИМХО, тебе дадут тесты. Сделай пару-тройку синтетических примеров с разветвленным множественным наследованием, замерь на таймере высокого разрешения - и ты получишь, как минимум, порядок в разности скоростей. Чесслово, рассуждений выше для тестов - выше крыши. А вот численный результат, уверен, был бы любопытен всем но насколько это плохо? |
Сообщ.
#55
,
|
|
|
Цитата JoeUser @ Но решение в каких-то числах, пусть в шкальных оценках, ИМХО, тебе дадут тесты. Пытался замерять такты, через clock(). там значения плавали, для одного и того же кода. При этом два диапазона значений, для двух разных кодов, были довольно близкими. Я не смог однозначно сказать, что какой-то код быстрее. Субъективно же чуточку ускорилось. Следовало сразу мерить профайлером, в более суровых и чистых условиях, а теперь поздняк метаться. На данный момент у меня нет действющих dynamic_cast. |
Сообщ.
#56
,
|
|
|
Цитата Eric-S @ Я не смог однозначно сказать, что какой-то код быстрее. Субъективно же чуточку ускорилось. На большлом компе проверял? проверь на железке с памятью 2М, и скоростью машины времен начала 90-х, хотя бы.Либо каждый замен времени умножай на 100 или 1000, и сравни есть ли разница, это че совсем точно,но лучше чем никак. |
Сообщ.
#57
,
|
|
|
Цитата settler @ Лучше повторять тест. Подобрать такое количество повторов, чтобы вместе они занимали 1-10 секунд. Тогда по крайней мере паре-тройке цифр в результате можно будет доверять.Либо каждый замен времени умножай на 100 или 1000, Цитата Eric-S @ Значит можно считать, что притормаживание несущественно. В любом случае основное время у тебя занимает обработка после проверки, и на фоне этого времени небольшое притормаживание dynamic_cast будет незаметно. там значения плавали, для одного и того же кода. При этом два диапазона значений, для двух разных кодов, были довольно близкими. Я не смог однозначно сказать, что какой-то код быстрее. |