Russian Qt Forum

Программирование => С/C++ => Тема начата: Yegor от Май 31, 2017, 21:08



Название: [РЕШЕНО] Использование dynamic-cast
Отправлено: Yegor от Май 31, 2017, 21:08
Всем здравствуйте!

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

А как на счет преобразования из базового класса на класс-примесь? Который есть дальше в структуре наследования.
Например, есть базовый класс. Он наследуется потомку. А тот потомок еще наследует и класс-примесь.

Можно ли, имея указатель на базовый класс, преобразовать его на класс-примесь? С помощью dynamic_cast.

Спасибо!


Название: Re: Использование dynamic-cast
Отправлено: Пантер от Май 31, 2017, 21:33
Нет, они же в разных иерархиях находятся.


Название: Re: Использование dynamic-cast
Отправлено: Igors от Июнь 01, 2017, 07:35
У меня это почему-то всегда работало :), но некоторые говорят что нет - ну наверное зависит от реализации. Во всяком случае это всегда можно сделать 2-мя dynamic_cast, так точно приведет


Название: Re: Использование dynamic-cast
Отправлено: __Heaven__ от Июнь 01, 2017, 07:38
Разве?
Код
C++ (Qt)
#include <iostream>
 
class Base {
public:
   virtual ~Base() {
 
   }
};
 
class Foo{
public:
   virtual ~Foo() {
 
   }
};
 
class FooBase: public Base, public Foo{    
};
 
int main()
{
   Base *base = new FooBase;
   Foo *foo = dynamic_cast<Foo *>(base);
   if (foo){
       std::cout << "yes";
   }
   else {
       std::cout << "no";
   }
}
 
Компиль имеет дерево наследования и знает как с помощью смещения указателя получить указатель на вторую базу.


Название: Re: Использование dynamic-cast
Отправлено: Пантер от Июнь 01, 2017, 09:26
Странно, был уверен, что нельзя. Посыпаю голову пеплом.
А стандарт это регламентирует?


Название: Re: Использование dynamic-cast
Отправлено: __Heaven__ от Июнь 01, 2017, 09:51
sidecast, кажется, это называется
Здесь в примере это происходит http://en.cppreference.com/w/cpp/language/dynamic_cast


Название: Re: Использование dynamic-cast
Отправлено: Igors от Июнь 01, 2017, 09:59
Код
C++ (Qt)
   Foo *foo = dynamic_cast<Foo *>(base);
 
Ну то ясно что приведет, но не о том спрашивалось
Код
C++ (Qt)
#include <stdio.h>
 
struct Root {
virtual ~Root( void ) {}
};
 
struct Leaf1 {
virtual ~Leaf1( void ) {}
};
 
struct Leaf2 {
virtual ~Leaf2( void ) {}
};
 
struct Tree : public Root, public Leaf1, public Leaf2 {
};
 
int main( void )
{
Tree * tree = new Tree;
Leaf1 * leaf1 = tree;
Leaf2 * leaf2 = dynamic_cast<Leaf2 *> (leaf1);
printf("Tree = %p, Leaf1 = %p, Leaf2 = %p\n", tree, leaf1, leaf2);
return 0;
}
Обратите внимание что Leaf1 и Leaf2 не связаны никакими отношениями наследования. Но у меня печатает все 3 адреса разные и ненулевые

А стандарт это регламентирует?
Вроде нет, на усмотрение реализации


Название: Re: Использование dynamic-cast
Отправлено: __Heaven__ от Июнь 01, 2017, 10:32
Пример из стандарта:
Код
C++ (Qt)
class A { virtual void f(); };
class B { virtual void g(); };
class D : public virtual A, private B { };
void g() {
   D d;
   B* bp = (B*)&d; // cast needed to break protection
   A* ap = &d; // public derivation, no cast needed
   D& dr = dynamic_cast<D&>(*bp); // fails
   ap = dynamic_cast<A*>(bp); // fails
   bp = dynamic_cast<B*>(ap); // fails
   ap = dynamic_cast<A*>(&d); // succeeds
   bp = dynamic_cast<B*>(&d); // ill-formed (not a run-time check)
}
 
Судя по всему, стандарт предполагает возврат 0 значения указателя при подобных кастах.
Кстати, смутила строка
Код
C++ (Qt)
B* bp = (B*)&d; // cast needed to break protection
А разве здесь правомерно взят адрес B? Кажется тут взят адрес А и преобразован к B.
P.S.: Пантер, к вопросу об отличии reinterpret_cast и pure C cast


Название: Re: Использование dynamic-cast
Отправлено: __Heaven__ от Июнь 01, 2017, 10:53
Хотя, тут в примере ситуация отягощается наличием приватного наследования...


Название: Re: Использование dynamic-cast
Отправлено: Igors от Июнь 01, 2017, 10:57
Кстати, смутила строка
Код
C++ (Qt)
B* bp = (B*)&d; // cast needed to break protection
А разве здесь правомерно взят адрес B? Кажется тут взят адрес А и преобразован к B.
Да, bp != &d, С каст автоматом выбирает наилучший вариант приведения, поэтому я всегда считал его "expert mode"  :)

Судя по всему, стандарт предполагает возврат 0 значения указателя при подобных кастах.
Хз, в справочнике пример sidecast с классами A. B, D. Повторюсь - у меня это всегда работало, но тут  (уже много лет назад) один паренек проверил мой пример и сказал что нет - ну я ему поверил, выглядел он прилично :)

По идее раскрутить все "дерево" можно, т.к. типы идентифицируются, напр для примера выше
Код
C++ (Qt)
Leaf1 * leaf1 = new Leaf1;
Leaf1 * leaf1_1 = tree;
Эти 2 экземпляра имеют один тип но разные VMT, т.е. второй знает что он часть Tree (а не сам по себе)


Название: Re: Использование dynamic-cast
Отправлено: Авварон от Июнь 01, 2017, 13:01
Странно, был уверен, что нельзя. Посыпаю голову пеплом.
А стандарт это регламентирует?

Уволен!


Название: Re: Использование dynamic-cast
Отправлено: Авварон от Июнь 01, 2017, 13:12
Пример из стандарта:
Код
C++ (Qt)
class A { virtual void f(); };
class B { virtual void g(); };
class D : public virtual A, private B { };
void g() {
   D d;
   B* bp = (B*)&d; // cast needed to break protection
   A* ap = &d; // public derivation, no cast needed
   D& dr = dynamic_cast<D&>(*bp); // fails
   ap = dynamic_cast<A*>(bp); // fails
   bp = dynamic_cast<B*>(ap); // fails
   ap = dynamic_cast<A*>(&d); // succeeds
   bp = dynamic_cast<B*>(&d); // ill-formed (not a run-time check)
}
 
Судя по всему, стандарт предполагает возврат 0 значения указателя при подобных кастах.
Кстати, смутила строка
Код
C++ (Qt)
B* bp = (B*)&d; // cast needed to break protection
А разве здесь правомерно взят адрес B? Кажется тут взят адрес А и преобразован к B.
P.S.: Пантер, к вопросу об отличии reinterpret_cast и pure C cast

Данный пример и не будет работать, потому что
Код:
B* bp1 = (B*)&d;
B* bp2 = &d; // допустим, там public
assert(bp1 == bp2); // false

Угадайте почему


Название: Re: Использование dynamic-cast
Отправлено: Авварон от Июнь 01, 2017, 13:17
А не, был не прав. В дебрях виртуального наследования, класс А в  памяти лежит ПОСЛЕ класса B. Тогда непонятно, почему он не видит vtable.
Судя по всему, динамик каст проверяет приватность наследования, других причин не вижу.


Название: Re: Использование dynamic-cast
Отправлено: __Heaven__ от Июнь 01, 2017, 13:18
Так а что угадывать? :)
Я же выше написал, что c++ преобразование в отличии от pure c делает смещение с учётом порядка наследования.


Название: Re: Использование dynamic-cast
Отправлено: __Heaven__ от Июнь 01, 2017, 13:19
Виртуальное наследование лежит последним??


Название: Re: Использование dynamic-cast
Отправлено: Авварон от Июнь 01, 2017, 13:20
Так а что угадывать? :)
Я же выше написал, что c++ преобразование в отличии от pure c делает смещение с учётом порядка наследования.

То, что вы оказались неправы:)
Я тоже думал, что берется адрес А, но это не так:
Код:
    qDebug() << quintptr(&d);
    qDebug() << quintptr(bp);
    qDebug() << quintptr(ap);

Код:
140735028670000
140735028670000
140735028670008


Название: Re: Использование dynamic-cast
Отправлено: Авварон от Июнь 01, 2017, 13:21
Виртуальное наследование лежит последним??

В данном случае, да. Интересно, как в случае множественного.


Название: Re: Использование dynamic-cast
Отправлено: Авварон от Июнь 01, 2017, 13:26
В данном случае, да. Интересно, как в случае множественного.

Да, все кладутся в конец в порядке наследования.

Код:
class D : virtual public A, virtual private B, private C

Код:
140730504186864 // d
140730504186872 // a
140730504186880 // b
140730504186864 // c

Ну и да:
Код:
class D : virtual public A, private B // сайд-каст не работает
class D : virtual public A, protected B // сайд-каст не работает
class D : virtual public A, public B // сайд-каст работает


Название: Re: Использование dynamic-cast
Отправлено: __Heaven__ от Июнь 01, 2017, 13:30
Цитировать
Да, все кладутся в конец в порядке наследования.
Точно? Я вижу несколько другую картину.


Название: Re: Использование dynamic-cast
Отправлено: ViTech от Июнь 01, 2017, 13:59
Цитировать
Да, все кладутся в конец в порядке наследования.
Точно? Я вижу несколько другую картину.

Думаю не стоит на этом заморачиваться. Разные компиляторы могут по разному vtable и данные объектов создавать и хранить, это уже их головная боль. Разработчикам главное понимать, как dynamic_cast по стандарту работать должен.


Название: Re: Использование dynamic-cast
Отправлено: Igors от Июнь 01, 2017, 14:41
Так а что угадывать? :)
Я же выше написал, что c++ преобразование в отличии от pure c делает смещение с учётом порядка наследования.
С-каст сделает то же самое для приведения "по вертикали", вот sidecross оно не потянет

Думаю не стоит на этом заморачиваться. Разные компиляторы могут по разному vtable и данные объектов создавать и хранить, это уже их головная боль. Разработчикам главное понимать, как dynamic_cast по стандарту работать должен.
Согласен, любое приведение легко разложить на "вертикальные", они должны работать железно. Ни к чему искать ненужных приключений


Название: Re: Использование dynamic-cast
Отправлено: Авварон от Июнь 01, 2017, 14:43
По стандарту side cast разрешен. Например, можно кастить один интерфейс к другому.
Или проверять, реализует ли данный QObject такой-то интерфейс.


Название: Re: Использование dynamic-cast
Отправлено: Yegor от Июнь 02, 2017, 16:16
Всем спасибо, кто откликнулся!