Russian Qt Forum

Программирование => С/C++ => Тема начата: Igors от Ноябрь 26, 2010, 12:31



Название: dynamic_cast и просто приведение - в чем разница?
Отправлено: Igors от Ноябрь 26, 2010, 12:31
Добрый день

Сегодня поплатился за свое пренебрежение ко всяким _cast.

Код
C++ (Qt)
CMaterialTarget * target = (CMaterialTarget *) translator;
int count = target->NumberOfChildren();
 
Это не вылетает, не ведет к утечкам, но.. count возвращается неправильный.
А так все хорошо, правильный
Код
C++ (Qt)
CMaterialTarget * target = dynamic_cast<CMaterialTarget *> (translator);
 
И как такое может быть?  :)


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: BRE от Ноябрь 26, 2010, 12:42
А как что определено?


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: Igors от Ноябрь 26, 2010, 12:49
А как что определено?
Ну так неинтересно - используют dynamic_cast многие (если не все) вот пусть и приведут пример когда оно не равно C приведению  :) (это вопрос для начинающих)


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: zenden от Ноябрь 26, 2010, 13:20
Что-то не могу догадаться. Ясно, что это какие-то хитросплетения наследования, механизма виртуальных функций.


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: RedDog от Ноябрь 26, 2010, 14:03
Что-то не могу догадаться. Ясно, что это какие-то хитросплетения наследования, механизма виртуальных функций.
когда, допустим, аргументом некой ф-цци является некий базовый класс, а в зависимости от конкретного переданного типа (наследников базового) выполняются разные условия.


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: Igors от Ноябрь 26, 2010, 14:31
когда, допустим, аргументом некой ф-цци является некий базовый класс, а в зависимости от конкретного переданного типа (наследников базового) выполняются разные условия.
"Некой..некий" - а можно проще или, еще лучше, на примере? Спасибо


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: pastor от Ноябрь 26, 2010, 14:53
Былобы неплохо увидеть иерархию классов и где и как объявлен NumberOfChildren(). Да, и объектом какого класса может быть translator?


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: RedDog от Ноябрь 26, 2010, 15:44
когда, допустим, аргументом некой ф-цци является некий базовый класс, а в зависимости от конкретного переданного типа (наследников базового) выполняются разные условия.
"Некой..некий" - а можно проще или, еще лучше, на примере? Спасибо
Код:
void on_click(QWidget *clickedWidget)
{
     QString caption("");
     QTextEdit *edit = dynamic_cast<QTextEdit *>(clickedWidget);
     if (edit)
          caption = edit->toPlainText():
     else
     QBushButton *button = dynamic_cast<QTextEdit *>(clickedWidget);
     if (button)
         caption = button->text();
    MainWindow->setWindowTitle(caption);
}


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: Igors от Ноябрь 26, 2010, 16:39
Код:
...
     else {
      QPushButton *button = dynamic_cast<QTextEdit *>(clickedWidget);
      if (button)
         caption = button->text();
     }
..
}
Я добавил скобочки (не суть), но здесь до выполнения дело не дойдет - компилятор вякнет что может присвоить один указатель другому, т.к. QPushButton не родитель QTextEdit.

Былобы неплохо увидеть иерархию классов и где и как объявлен NumberOfChildren(). Да, и объектом какого класса может быть translator?
NumberOfChildren - виртуальный метод. Переменная translator указатель на объект типа CTranslator, который является потомком CMaterialTarget. Правда, есть еще одна маленькая деталь (из-за которой нельзя приводить по-народному)...


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: RedDog от Ноябрь 26, 2010, 16:48
Кастится не от QPushButton, а от их общего базового класса QWidget

Код:
void on_click(QWidget *clickedWidget)
{
     QString caption("");
     QTextEdit *edit = dynamic_cast<QTextEdit *>(clickedWidget);
     if (edit)
          caption = edit->toPlainText():
     else{
     QBushButton *button = dynamic_cast<QBushButton *>(clickedWidget); // ошибка тут была
     if (button)
         caption = button->text();
    }
    MainWindow->setWindowTitle(caption);
}
подправил код


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: pastor от Ноябрь 26, 2010, 17:57
NumberOfChildren - виртуальный метод. Переменная translator указатель на объект типа CTranslator, который является потомком CMaterialTarget. Правда, есть еще одна маленькая деталь (из-за которой нельзя приводить по-народному)...

А какой тип наследования? (public, protected, ...)


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: Igors от Ноябрь 27, 2010, 13:42
Кастится не от QPushButton, а от их общего базового класса QWidget
То ясно, просто после каста указатели должны сбиваться. Против Вашего примера я не возражаю, конечно если я приведу QTextEdit к QPushButton - то вылетит. Но если виджет QTextEdit, то прямое приведение имеет тот же эффект что и dynamic_cast (в Вашем примере). Интересен случай когда 2 приведения возвращают разные (ненулевые) указатели

А какой тип наследования? (public, protected, ...)
В оригинале был "virtual public". Я попробовал просто "public" - результат тот же


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: RedDog от Ноябрь 27, 2010, 22:35
Кастится не от QPushButton, а от их общего базового класса QWidget
То ясно, просто после каста указатели должны сбиваться. Против Вашего примера я не возражаю, конечно если я приведу QTextEdit к QPushButton - то вылетит. Но если виджет QTextEdit, то прямое приведение имеет тот же эффект что и dynamic_cast (в Вашем примере). Интересен случай когда 2 приведения возвращают разные (ненулевые) указатели

А какой тип наследования? (public, protected, ...)
В оригинале был "virtual public". Я попробовал просто "public" - результат тот же
В моем примере проверяется какого конкретно класса экземпляр был передан в качестве параметра. Т.к. у каждого  класса разные интерфейсы, то если C-style сделать и не проверить, то можно на AV нарваться, т.к. будет доступ к несуществующему методу.


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: ufna от Ноябрь 27, 2010, 23:17
Ну приведение "сишное" будет в статической сборке падать на ура :)

QObject'а потомков так не следует. А если еще добавить интерфейсы в наследование кутэшные, то там вообще веселье с кастами начинается.


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: Авварон от Ноябрь 28, 2010, 03:16
Igors
Да хоспади, наследование от 2х виртуал классов - сишный каст не меняет значение указателей, а для динамик каст адрес 2го класса == адрес базы + сайзов(1го).
И как бы ваши задачки достали:) Напоролись на что-то - выкладывайте сразу ответ. А то я 5 минут плевался о том что "делать мне больше нечего" и 1 минуту думал вам решение.


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: zenden от Ноябрь 28, 2010, 12:37
Авварон
приведите, пожалуйста, работающий код


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: Авварон от Ноябрь 28, 2010, 12:59
zenden
Я не тестил, но по идее что-то типа:
Код:
class Base
{
}
class IFace1
{
    virtual int f() {}
}
class IFace2
{
    virtual int g() {}
}
class Derived : public Base, IFace1, IFace2
{
    virtual int f() {}
    virtual int g() {}
}
void h()
{
   Base *o = new Derived;
   IFace1 *o1 = (IFace1*)o; // ok, vtable совпадают, тк у Base нет вирт ф-ий
   IFace2 *o2 = (IFace2*)o; // fail, мы читаем vtable 1го класса, а должны 2го
}

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


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: Igors от Ноябрь 28, 2010, 16:36
Напоролись на что-то - выкладывайте сразу ответ. А то я 5 минут плевался о том что "делать мне больше нечего" и 1 минуту думал вам решение.
Ну я ж предупредил - вопрос для начинающих. А от сразу выложенного ответа немного толку - все так много знают ... :)

Ваш код правильный, но заметим что dynamic_cast может и больше, напр.

Код
C++ (Qt)
  Derived *o = new Derived;  // вмещающий класс
  IFace1 *o1 =  dynamiс_cast <IFace1*> (o);  // здесь С приведение имеет тот же эффект
  IFace2 *o2 =  dynamiс_cast <IFace2*> (o);  // здесь тоже
  ...
  IFace1 *o3 =  dynamiс_cast <IFace1*> (o2);  // а здесь С приведение даст неверный результат
}
Последнее приведение и было на что я напоролся. Все тоже самое можно и с Base * (вместо Derived *) если Base имеет virtual(ы).

Как я понял, dynamiс_cast умеет как-то "запрыгнуть" на "полную" VMT (с каким типом объект создавался) и оттуда уже смотреть. Замечание: (мой) отладчик показывает приведенные указатели одинаковые, но это не так если их напечатать. "С" приведение также меняет указатель, но не всегда удачно  :)


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: spectre71 от Ноябрь 28, 2010, 19:18
zenden
Я не тестил, но по идее что-то типа:
Код:
class Base
{
}
class IFace1
{
    virtual int f() {}
}
class IFace2
{
    virtual int g() {}
}
class Derived : public Base, IFace1, IFace2
{
    virtual int f() {}
    virtual int g() {}
}
void h()
{
   Base *o = new Derived;
   IFace1 *o1 = (IFace1*)o; // ok, vtable совпадают, тк у Base нет вирт ф-ий
   IFace2 *o2 = (IFace2*)o; // fail, мы читаем vtable 1го класса, а должны 2го
}

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

Все в данном примере нормально. Никаких  fail не может быть.

Извиняюсь. Не ообратил внимание на:
Base *o = new Derived;

Но это не потому что у Base нет виртуальных функций, а потому что  Base  не связана наследованием с IFace1  и IFace2.

А вот так все нормально:
Код
C++ (Qt)
void h()
{
  Derived*o = new Derived;
  Base*b = (Base*)o;
  IFace1 *o1 = (IFace1*)o;
  IFace2 *o2 = (IFace2*)o;
}


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: spectre71 от Ноябрь 28, 2010, 19:22
Как я понял, dynamiс_cast умеет как-то "запрыгнуть" на "полную" VMT (с каким типом объект создавался) и оттуда уже смотреть. Замечание: (мой) отладчик показывает приведенные указатели одинаковые, но это не так если их напечатать. "С" приведение также меняет указатель, но не всегда удачно  :)


Это просто ошибка!
IFace1 *o3 =  dynamiс_cast <IFace1*> (o2);
Пытаемся приводить IFace2 к IFace1. Какой в этом смысл? Они не связаны отношением наследования.




Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: Igors от Ноябрь 28, 2010, 19:42
Все в данном примере нормально. Никаких  fail не может быть.
Ну не надо так придираться к мелочам - идея совершенно правильная  :) Рухнет если Base будет иметь хотя бы 1 член данных. Если же Base будет иметь virtual - то имеем тяжелые ошибки на выполнении. А без virtual компилятор не позволит написать dynamic_cast<Base *>

Это просто ошибка!
IFace1 *o3 =  dynamiс_cast <IFace1*> (o2);
Пытаемся приводить IFace2 к IFace1. Какой в этом смысл? Они не связаны отношением наследования.
Не связаны. Но если оба базовые классы, то можно перескочить с o1 на o2 и обратно через dynamic_cast (этого я не знал  :)). И получить указатель на наследующий класс (Derived *) тоже можно.


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: spectre71 от Ноябрь 28, 2010, 19:53
Не связаны. Но если оба базовые классы, то можно перескочить с o1 на o2 и обратно через dynamic_cast (этого я не знал  :)). И получить указатель на наследующий класс (Derived *) тоже можно.

А зачем так делать? Что за модель данных этого может потребовать?



Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: Авварон от Ноябрь 28, 2010, 20:01
А вот так все нормально:
Код
C++ (Qt)
void h()
{
  Derived*o = new Derived;
  Base*b = (Base*)o;
  IFace1 *o1 = (IFace1*)o;
  IFace2 *o2 = (IFace2*)o;
}
Не поверите, тут даже касты не нужны)))


Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: spectre71 от Ноябрь 28, 2010, 20:08
А вот так все нормально:
Код
C++ (Qt)
void h()
{
  Derived*o = new Derived;
  Base*b = (Base*)o;
  IFace1 *o1 = (IFace1*)o;
  IFace2 *o2 = (IFace2*)o;
}
Не поверите, тут даже касты не нужны)))

Согласен!

Но в приведенном вами примере и dynamic_cast не поможет - Base не виртуальный. А вот если сделать его виртуальным, то да:
dynamic_cast - работает
() - не работает

Однако никакого смысла прыгать по веткам не вижу!



Название: Re: dynamic_cast и просто приведение - в чем разница?
Отправлено: Igors от Ноябрь 28, 2010, 20:46
Но в приведенном вами примере и dynamic_cast не поможет - Base не виртуальный. А вот если сделать его виртуальным, то да:
dynamic_cast - работает
() - не работает
Да, но речь идет о таком коде (полагаем что Base виртуальный)
Код
C++ (Qt)
  Base * b = new Derived();
  IFace1 *o1 = (IFace1*) b;
  IFace2 *o2 = (IFace2*) b;
}
Это будет работать корректно с dynamic_cast, но не без него

Однако никакого смысла прыгать по веткам не вижу!
Да я тоже не видел, но пришлось  :) Эквивалентно 2 dynamic_cast - взять вмещающий класс и от него
нужный базовый