LINUX.ORG.RU

[C++]Продолжение темы по *_cast


0

1

Итак, уважаемые друзья, недавно мой друг dikar отпостил вопросы по поводу того зачем нужен *cast( тема Здесь). Так вот, пораскинув своими мозгами, мы поняли, что донесли до вас нашу спорную ситуацию не верно...И вот теперь исправляемся...

Значит дело было в следующем. На работе как и говорилось пишем визуальный интерфейс на Qt. Так как пишется он уже очень давно, то и коду выросло не мало. В один момент, по нажатию кнопки, у нас выскакивает наш написанный класс VIDACHA - потомок от QDialog. В этом классе живет другой наш класс. Это класс наследован от QWidget и называется Forms. Конструктор каждого из них принимает указатель на QWidget( прошу заметить, что сами классы между собой никак не связаны , но класс Forms при своем создании принимает указатель класса VIDACHA).Т.е:

class VIDACHA : public QDialog {
   VIDACHA( QWidget * = 0 );

   Forms *forms;
   //Другие виджеты
};

class Forms : public QWidget {
   Forms( QWidget * = 0 );

   public:
      void someFunction();
};

void Forms :: someFunction() {
   //Какие-то манипуляции после которого надо закрыть весь QDialog
   // Вот так мы закрыли QDialog
   VIDACHA *papa = dynamic_cast< VIDACHA* >( parent() );
   if( papa != NULL )
      papa -> close();
}

Это предложил я, вот тут у нас и возник спор. Мой друг и коллега dikar  это дело все удалил и сделал следующее:
void Forms :: someFunction() {
   //Какие-то манипуляции после которого надо закрыть весь QDialog
   // Вот так мы закрыли QDialog
   reinterpret_cast< VIDACHA* >( parent() ) -> close();
}

Вопрос, как правильно поступить в этой ситуации?

>reinterpret_cast

В одной умной книжке(Страуструп) было сказано «reintepret_cast must be used ONLY if you are really sure, what are you doing». Ну или как-то так. Ваш вариант с dynamic_cast помедленнее, зато на 100% гарантирует то, что программа не сделает segfault, если не удастся произвести cast.

Pinkbyte ★★★★★ ()

а, и да - название класса VIDACHA рядом с соседствующим названием Forms - это, простите, моветон. Разработайте coding standarts что-ли... По крайней мере - в сфере используемых имен

Pinkbyte ★★★★★ ()
Ответ на: комментарий от Insomnium

Почему обязательно надо говорить что чужой код говно, при этом самому ничего не предлагать в замен...?Говорить что чей то код хреновый и при этом не исправлять ошибки, даже не указывать на них может каждый... А глаза здесь нечего ломать, здесь ничего сверхъестественного нету...

energyclab ()
Ответ на: комментарий от Pinkbyte

Согласен, название VIDACHA хреновое название... За

«reintepret_cast must be used ONLY if you are really sure, what are you doing»

спасибо, веселый стёб жизни. Поясните почему dynamic_cast работает медленнее?

energyclab ()
Ответ на: комментарий от energyclab

Вообще dynamic_cast быстрее, более гибок и можно сказать безопаснее. С reinterpret_cast нужно быть осторожным и как уже написали выше - нужно быть уверенным что ты делаешь, если собираешься его юзать. Что использовать - решать вам, но я бы оставил dynamic_cast. А по поводу оформления кода - у вас нет своего стандарта? Да даже если и так и проект небольшой, эти VIDACHA, papa, пробелы где попало >_< самим должно быть неприятно.

Insomnium ★★★★ ()
Ответ на: комментарий от energyclab

dynamic_cast просматривает виртуальную таблицу классов, проверяет можно ли привести тип, если возможно, то возвращает указатель, в противном случае NULL. reinterpret_cast «рубит с плеча», да, это быстрее. :}

Insomnium ★★★★ ()
Ответ на: комментарий от energyclab

Если вы уверены что parent это то что вам надо - static_cast, если нет - dynamic_cast. reinterpret_cast это более ахтунговая вещь. А вообще плохо, что у вас оба класса знают друг о друге, такое лучше не допускать.

Booster ★★ ()

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

Booster ★★ ()
Ответ на: комментарий от Booster

В Qt вроде нету возможности избежать зависимости виджет_родитель-виджет_потомок, там на этом всё и построено. Не знаю точно можно ли избежать связи родитель->потомок, но то что потомок будет всегда знать о своем родителе благодаря методу QObject * parent() это точно.

Dikar ★★ ()

Под уверенностью я имел ввиду, что если вы точно уверены что parent это 100% VIDACHA, то тогда определённо static_cast. Если точно не знаете, то dynamic.

Booster ★★ ()
Ответ на: комментарий от Dikar

>В Qt вроде нету возможности избежать зависимости виджет_родитель-виджет_потомок, там на этом всё и построено. Не слишком здорово.

Если это возможно, то касты лучше вообще убрать. Сразу предавать в конструктор указатель на необходимый тип, а не его базовый.

Booster ★★ ()
Ответ на: комментарий от Insomnium

эти VIDACHA, papa, пробелы где попало >_< самим должно быть неприятно.

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

energyclab ()
Ответ на: комментарий от Insomnium

Мне не все нравится в Qt, поэтому я стараюсь если есть возможность сделать замену на аналог из стандарта, то я его делаю, а потом qobject_cast я даже и не искал для его целей в мануалах

energyclab ()
Ответ на: комментарий от Booster

А вообще плохо, что у вас оба класса знают друг о друге, такое лучше не допускать.

ничего подобного. там только связь предка с родителем, а потом, если у меня QDialog содержит динамически изменяющиеся виджеты, и мне надо закрыть это окно по отработке события одного из виджетов, то как мне еще это сделать, если не через parent? Поясни, за помощь буду благодарен... Класс VIDACHA представляет собой некоторый компоновачный объект, а класс Forms (там и другие подобные объекты имеются) один из визуальных компонентов этого окна. Причем некоторые визуальные компоненты является динамическими (например есть checkbox по нажатию которого окно изменяется динамически)

energyclab ()
Ответ на: комментарий от Booster

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

energyclab ()
Ответ на: комментарий от energyclab

Сигналы это самое лучшее, лень не оправдание. ^) Можно ещё объявить конструктор принимающий только VIDACHA, а в производном классе сделать дополнительный метод, который будет приводить к VIDACHA и возвращать его, но понятно что всё равно слоты намного лучше.

Booster ★★ ()
Ответ на: комментарий от Dikar

у любого родителя есть метод child(), который является методом QObject, так что избежать связи родитель -> ребенок не получится, а потом если это и сделать вручную, то при вызове деструткора, объекты, которые будут физически отцеплены от своих родителей не будут удалены,и память не будет возвращена компьютеру, так как деструктор QObject на сколько я знаю, является виртуальным, а значит, вызов деструктора ребенка не произойдет как раз на разрыве их связи

energyclab ()
Ответ на: комментарий от Booster

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

energyclab ()
Ответ на: комментарий от energyclab

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

Что? Сигналы работают через вызов функций по указателю, никаких потерь нет и быть не может, всё максимально быстро.

А потом, я еще раз повторяю, почему бы в этой ситуации не использовать dynamic_cast, если он как раз для таких случаев и придуман???

Если что-то придумано, то это не означает что это лучший выбор. Но способ годный, я не спорю.

Booster ★★ ()
Ответ на: комментарий от Booster

По поводу reinterpret_cast. На сколько я понимаю эту операцию она должна быть использована только там, где необходима временная интерпретация целевого операнда. Например, если функция записи в файл принимает const char*, а я хочу передать структуру из различных полей, или класс, то эту совокупность байтов я могу попросить интерпретировать как const char*. вот здесь то как раз и нужен данный cast. Это своего рода замена приведению типов из С, когда надо было указать просто в скобках приводимый тип

energyclab ()
Ответ на: комментарий от Booster

Механизм СИГНАЛ-СЛОТ построен на механизме событий, это факт. При решении моей проблемы мне бы пришлось создать свой сигнал в классе VIDACHA и прицепить его там на слот close(), а в классе Forms там где надо было бы закрыть окно, пришлось бы генерить этот сигнал через emit. А бедный event добавил бы к себе в списки еще один сигнал, для того чтобы потом его передать получателю

energyclab ()
Ответ на: комментарий от energyclab

В общем да, reinterpret_cast это интерпретация куска памяти одного типа в другой тип. static_cast и dynamic_cast в обязательном порядке учитывают иерархию наследования и внутреннюю структуру типа, даже могут менять возвращаемые указатели(при множественном наследовании). reinterpret_cast можно использовать разве что для приведения структур данных и то только так называемых pod структур. Иначе там почти всегда будет UB, так как тупо интерпретировать кусок памяти одного типа в другой без учёта их внутреннего строения это ахтунг.

Booster ★★ ()
Ответ на: комментарий от energyclab

>Механизм СИГНАЛ-СЛОТ построен на механизме событий, это факт.

Да он так называется и выглядит так. Но внутренняя работа с помощью прямого вызова функции по указателю. Кутешники писали, что сигналы это на самом деле безопасный аналог сишного коллбэка(указателя) функции.

Booster ★★ ()
Ответ на: комментарий от Booster

> Что? Сигналы работают через вызов функций по указателю, никаких потерь нет и быть не может, всё максимально быстро.

Не совсем так, если использовалось QueuedConnection или AutoConnection с сигналом из другого треда, то используется eventloop.

rival ★★ ()
Ответ на: комментарий от energyclab

> генерить этот сигнал через emit

Ты ведь в курсе, что emit это пустышка, сделанная для читабельности, и можно ее не использовать?

rival ★★ ()
Ответ на: комментарий от rival

>Не совсем так, если использовалось QueuedConnection или AutoConnection с сигналом из другого треда, то используется eventloop.

Тогда не знаю, я читал в доке что это именно безопасный коллбек. QueuedConnection или AutoConnection юзать не доводилось. А в чём их смысл?

Booster ★★ ()
Ответ на: комментарий от Booster

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

energyclab ()
Ответ на: комментарий от rival

Нет не в курсе, можно подробнее, на сколько я понимаю, это обычный макрос Qt, он как раз то и говорит eventloopу( никак не мог вспомнить название это, спасибо что подсказал)))) что есть действия на обработку

energyclab ()
Ответ на: комментарий от energyclab

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

Зачем? Сигнал посылается конкретному объекту и может напрямую быть вызван нужный колбэк.

Booster ★★ ()
Ответ на: комментарий от Booster

[url=http://www.qtinfo.ru/signalsandslots]вот здесь[/url] не плохо написано что для чего. А так мне самому ни разу не доводилось их использовать, она всегда автоматом ставятся, те которые как видимо правильные, а так ты сам может немного расширить с их помощью свои возможности

energyclab ()
Ответ на: комментарий от Booster

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

energyclab ()
Ответ на: комментарий от energyclab

Тогда да, согласен. Про пустышку не как понимаю не ко мне. :)

Booster ★★ ()
Ответ на: комментарий от Booster

Если это возможно, то касты лучше вообще убрать. Сразу предавать в конструктор указатель на необходимый тип, а не его базовый.

Опять же это вопрос к разработчикам Qt3 ( да-да-да, всё еще 3.3.8 так как платформа специфическая и, можно сказать, сдохшая ).

Ветка наследования Qt-классов к нашему выглядит примерно так: Qt-> ... -> QObject -> ... -> QWidget -> ... -> QDialog -> VIDACHA.

В классе QObject присутствует метод

QObject * QObject :: parent( void );  // возвращает указатель на родителя виджета.

А конструкторы объектов классов этой ветки наследования ниже QWidget строятся примерно по такой схеме:

MyClass :: MyClass ( QWidget * parent, // Указатель на виджет - предок
                     int foo,          // параметр 1
                     ...
                     QString bar,      // параметр N
                     char * name )     // имя объекта, периодически необходимое для отладки

То есть, все что ниже QWidget в ветке наследования должно иметь родителем-виджетом объект, являющийся наследником или самим QWidget. При этом parent() в любом случае возвращает QObject * . Вот такой вот цирк.

Dikar ★★ ()
Ответ на: комментарий от Dikar

В классе QObject присутствует метод

Правильно, и именно здесь она и должна быть и именно с этим возвращаемым типом (и она естественно virtual ), так как свойство родитель будет присуще любому классу в QT, а по скольку не известно на какой ступени может понадобиться указатель на папу, логично сделать возвращаемый тип QObject*. И мало того он может иметь значение NULL.А поскольку указатель любого типа всегда n байт, то в него можно запихнуть любой объект. В принципе reinterpret_cast здесь тоже подходит, однако в случае если мы не угадаем с целевым операндом, то это однозначно приведет к иным результатам (close() отработает правильно для своего объекта, в результате закроется что то другое, если какая то другая функция, не имеющая такой длинной цепочки по virtual, будет так вызвана, то это может привести и к ошибке сегментирования )Так что dynamic действительно правильное решение

energyclab ()
Ответ на: комментарий от energyclab

1. parent имеет прототип : QObject * QObject :: parent( void ) const, то есть не виртуальный и это здесь не надо.

2. в системе x86 указатель занимает не n байт, а 4

3. если не известно на какой ступени наследования понадобится parent, то можно было тип возвращаемого значения вообще void* сделать.

4. reinterpret_cast ушел в прошлое, я понял что это косячный метод в нашем случае, но так как мы точно значем что за класс лежит по указателю this->parent(), то можно обойтись и static_cast.

Я думаю, что в этом топике все всё поняли. Спасибо всем за помощь.

Dikar ★★ ()

Ещё красноречивый пример отличия: static_cast и dynamic_cast используют операторы преобразований типов(как встроенные, так и кастомные). Если с помощью static_cast привести int к float, то получим 1 -> 1.0f. А если сделаем такое преобразование с помощью reintepret_cast, то получим совсем не 1.0f, так как не будет выполнена операция преобразования значения. Но иногда нужно именно такое поведение.

Booster ★★ ()
Ответ на: комментарий от Booster

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

так отвечать на вопрос можно только нубью одни сопли и никаких фактов что значит «худших» ? нужны технические подробности а такие советы только малыши хавают на веру

anonymous ()
Ответ на: комментарий от anonymous

Согласен. Но потом я написал что другие операторы делают дополнительную работу(операторы преобразования, учёт внутренней структуры, смотрят на гомоморфность(соответствие одной и тоже иерархии классов)), а reinterpret_cast просто интерпретирует кусок памяти одного типа, в другой. По-этому reinterpret_cast и есть самый небезопасный.

Booster ★★ ()
Ответ на: комментарий от Boy_from_Jungle

> оба неправы

Я бы не стал доверять постам существа с ником Boy_from_Jungle, находящихся в Development - ветке Linux - форума.

Dikar ★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.