Přetypování pomocí dynamic_cast

webhope

Přetypování pomocí dynamic_cast
« kdy: 19. 11. 2013, 11:59:12 »
Zdravím,
možná mi můžete pomoc. Dostal jsem se k článku přetypování s pomocí dynamic_cast při vícenásobné dědičnosti.
Ale mám problém, ten článek mě přejde nepřehledný, vůbec nevím jak spolu ty kódy souvisí, je to zmatek, chaos. Chápete to někdo?
Autor tam používá metody prvni(), druha() které nejsou nikde definované, v druhém kódu to samé.

http://www.builder.cz/rubriky/c/c--/problemy-s-typy-pri-vicenasobne-dedicnosti-155865cz

Jestli to někdo můžete nějak lidsky vysvětlit, aby se v tom člověk vyznal...?
« Poslední změna: 19. 11. 2013, 12:17:55 od Petr Krčmář »


webhope

Re:Článek na builderu - nerozumím funkci
« Odpověď #1 kdy: 19. 11. 2013, 12:15:58 »
Beru zpět, že tam není definice. Přehlédl jsem tu definici protože ten blok byl uvedený na stejném řádku jako slovo virtual (hledal jsem tu definici externě definovanou).

Jinak ale nerozumím větě:
"Vytvořil jsem 2 funkce, které vypíšou adresu, na kterou se odkazuje ukazatel daný jako parametr"
Která část má vypisovat tu adresu a jak?
Myslí toto?
Kód: [Vybrat]
cout << "second this=" << this << endl;
Jako že this vrací adresu?

Jinak ale nerozumím větě: "Funkce se liší jen typem parametru. " Já v nich žádný rozdílný parametr nevidím.

Obě funkce jsou definované stejně:
Kód: [Vybrat]
funkce_1(FirstSuperClass *objekt)
funkce_2(FirstSuperClass *objekt)
ani žádné přetypování tam není.

bob

Re:Článek na builderu - nerozumím funkci
« Odpověď #2 kdy: 19. 11. 2013, 12:23:19 »
Jinak ale nerozumím větě:
"Vytvořil jsem 2 funkce, které vypíšou adresu, na kterou se odkazuje ukazatel daný jako parametr"
Která část má vypisovat tu adresu a jak?
Myslí toto?
Kód: [Vybrat]
cout << "second this=" << this << endl;
Jako že this vrací adresu?

spravne - klucove slovo "this" je premenna obsahujuca adresu daneho objektu (instancie triedy)

Jinak ale nerozumím větě: "Funkce se liší jen typem parametru. " Já v nich žádný rozdílný parametr nevidím.

Obě funkce jsou definované stejně:
Kód: [Vybrat]
funkce_1(FirstSuperClass *objekt)
funkce_2(FirstSuperClass *objekt)
ani žádné přetypování tam není.

precitajte si to este raz:
void funkce1(DruhaNadTrida *objekt)
void funkce2(PrvniNadTrida *objekt)

webhope

Re:Přetypování pomocí dynamic_cast
« Odpověď #3 kdy: 19. 11. 2013, 12:42:07 »
Jo tak - já si již od dob co jsem používal PHP asociuji this jako objekt. A je to teda pointer nebo reference? Hádám, že pointer.

Aha, tak už ten rozdíl vidím. Ale ty dvě třídy PrvniNadTrida a DruhaNadTrida jsou principově totožné, ne?
Jde jen o to, že rozdíl je v tom, že funkce_1 má v parametru typ té druhé třídy a funkce_2 má v parametru typ té první třídy. Takže první volá funkci druha() z druhé nadTřídy a druhá volá funkci prvni() z první nadTřídy... Říkám to správně? A to přetypování je kde?

noname

Re:Přetypování pomocí dynamic_cast
« Odpověď #4 kdy: 19. 11. 2013, 14:36:16 »
Jo tak - já si již od dob co jsem používal PHP asociuji this jako objekt. A je to teda pointer nebo reference? Hádám, že pointer.

Aha, tak už ten rozdíl vidím. Ale ty dvě třídy PrvniNadTrida a DruhaNadTrida jsou principově totožné, ne?
Jde jen o to, že rozdíl je v tom, že funkce_1 má v parametru typ té druhé třídy a funkce_2 má v parametru typ té první třídy. Takže první volá funkci druha() z druhé nadTřídy a druhá volá funkci prvni() z první nadTřídy... Říkám to správně? A to přetypování je kde?
To cpp bych musel trošku oprášit, abych odpověděl perfektně, ale v zásadě se přetypování děje v rámci dědičnosti, aniž by člověk použil jakýkoliv operátor pro přetypování tím, že využívá jako typ proměnné typ předka, za kterou je možno dosadit typ potomka. Přetypování pomocí dynamic cast jsem ale moc nehověl a teď bych si ho musel znovu nastudovat, takže tady raději počkám, až to přečte někdo fundovanější. Pokud se dlouho nikdo nenajde, mrknu na to později odpoledne nebo večer, teď se musím učit něco jiného.


Sten

Re:Přetypování pomocí dynamic_cast
« Odpověď #5 kdy: 19. 11. 2013, 14:44:20 »
Aha, tak už ten rozdíl vidím. Ale ty dvě třídy PrvniNadTrida a DruhaNadTrida jsou principově totožné, ne?
Jde jen o to, že rozdíl je v tom, že funkce_1 má v parametru typ té druhé třídy a funkce_2 má v parametru typ té první třídy. Takže první volá funkci druha() z druhé nadTřídy a druhá volá funkci prvni() z první nadTřídy... Říkám to správně? A to přetypování je kde?

Sice vypadají velmi podobně, ale jsou to dvě různé třídy a uvnitř PodTrida zabírají dvě různé pozice, proto se i jejich adresa bude lišit. Interně si třídu PodTrida můžete představit takto:
Kód: [Vybrat]
ukazatel na vtable sdílený pro PrvniNadTrida a PodTrida
// členské proměnné třídy PrvniNadTrida, pokud by byly deklarovány
ukazatel na vtable specifický pro DruhaNadTrida
// členské proměnné třídy DruhaNadTrida, pokud by byly deklarovány
// členské proměnné třídy PodTrida, pokud by byly deklarovány

Začátek dat pro PodTrida a PrvniNadTrida (ukazatel na jejich vtable) je na stejném místě, takže pod == static_cast<PrvniNadTrida*>(pod). (Pokud vyrobím instanci třídy PrvniNadTrida, bude ukazatel na její vtable ukazovat jinam než ukazuje v instanci třídy PodTrida, ale bude na stejném místě relativně k PrvniNadTrida*.) Naproti tomu začátek dat pro DruhaNadTrida (ukazatel na vtable specifický pro danou třídu) je posunutý o délku ukazatele vtable pro PrvniNadTrida (plus případné členské proměnné, pokud by byly deklarovány), a tak ukazatel získaný přetypováním na DruhaNadTrida* bude o tuto délku posunutý, takže pod < static_cast<DruhaNadTrida*>(pod). (Ukazatel na vtable v instanci třídy DruhaNadTrida bude opět ukazovat jinam než ukazatel specifický pro DruhaNadTrida v instanci třídy PodTrida, ale opět bude na stejném místě relativně k DruhaNadTrida*.)

To přetypování je při volání těch metod implicitní (nemusí se uvádět), protože jde vždy převést z potomka na předka a kompilátor ví, že PrvniNadTrida i DruhaNadTrida jsou předky PodTrida.

Na druhou stranu je dobré vědět, že tohle nemá efekt na nullptr, takže testování na nullptr bude správně fungovat i po převodu:
Kód: [Vybrat]
PodTrida *pod = nullptr;
if (pod != nullptr)
funkce1(pod); // neprovede se, pod je nullptr
DruhaNadTrida *nad = pod;
if (nad != nullptr)
funkce1(nad); // neprovede se, nad je taky nullptr

webhope

Re:Přetypování pomocí dynamic_cast
« Odpověď #6 kdy: 19. 11. 2013, 16:50:53 »
Myslím že je to špatně:

Kód: [Vybrat]
if (pod != nullptr)
funkce1(pod); // neprovede se, pod je nullptr
DruhaNadTrida *nad = pod;
if (nad != nullptr)
funkce1(nad);

Podle toho článku to selže a musí se použít dynamic_cast pro porovnávání s vícenásobnou dědičností.

Ale možná se pletu protože jsem to dnes už zavřel, zřejmě zítra bych ty kódy všechny otestoval, zatím je to pro mě jen teorie, tak si to musím vyzkoušet.

Zatím díky

Sten

Re:Přetypování pomocí dynamic_cast
« Odpověď #7 kdy: 19. 11. 2013, 22:47:30 »
Myslím že je to špatně:

Kód: [Vybrat]
if (pod != nullptr)
funkce1(pod); // neprovede se, pod je nullptr
DruhaNadTrida *nad = pod;
if (nad != nullptr)
funkce1(nad);

Podle toho článku to selže a musí se použít dynamic_cast pro porovnávání s vícenásobnou dědičností.

Ale možná se pletu protože jsem to dnes už zavřel, zřejmě zítra bych ty kódy všechny otestoval, zatím je to pro mě jen teorie, tak si to musím vyzkoušet.

Zatím díky

Není nic jednoduššího, než to zkopírovat a vyzkoušet ;)

Ten článek pojednává pouze o existujících instancích, nullptr je ale výjimka:
Kód: (c++) [Vybrat]
#include <iostream>

class PrvniNadTrida
{
public:
virtual void prvni()
{}
};

class DruhaNadTrida
{
public:
virtual void druhy()
{}
};

class PodTrida
: public PrvniNadTrida
, public DruhaNadTrida
{};

int main()
{
PodTrida *pod = nullptr;
PrvniNadTrida *prvni = pod;
DruhaNadTrida *druha = pod;
std::cout << pod << " = " << prvni << " = " << druha << std::endl;
std::cout << (pod == prvni ? "pod == prvni" : "pod != prvni") << " && "
<< (pod == druha ? "pod == druha" : "pod != druha") << std::endl;
// prvni == druha nejde udělat bez přetypování
std::cout << (static_cast<void*>(pod) == static_cast<void*>(prvni) ? "pod == prvni" : "pod != prvni") << " && "
<< (static_cast<void*>(pod) == static_cast<void*>(druha) ? "pod == druha" : "pod != druha") << " && "
<< (static_cast<void*>(prvni) == static_cast<void*>(druha) ? "prvni == druha" : "prvni != druha") << std::endl;
std::cout << (dynamic_cast<void*>(pod) == dynamic_cast<void*>(prvni) ? "pod == prvni" : "pod != prvni") << " && "
<< (dynamic_cast<void*>(pod) == dynamic_cast<void*>(druha) ? "pod == druha" : "pod != druha") << " && "
<< (dynamic_cast<void*>(prvni) == dynamic_cast<void*>(druha) ? "prvni == druha" : "prvni != druha") << std::endl;

pod = new PodTrida();
prvni = pod;
druha = pod;
std::cout << pod << " = " << prvni << " = " << druha << std::endl;
std::cout << (pod == prvni ? "pod == prvni" : "pod != prvni") << " && "
<< (pod == druha ? "pod == druha" : "pod != druha") << std::endl;
// prvni == druha nejde udělat bez přetypování
std::cout << (static_cast<void*>(pod) == static_cast<void*>(prvni) ? "pod == prvni" : "pod != prvni") << " && "
<< (static_cast<void*>(pod) == static_cast<void*>(druha) ? "pod == druha" : "pod != druha") << " && "
<< (static_cast<void*>(prvni) == static_cast<void*>(druha) ? "prvni == druha" : "prvni != druha") << std::endl;
std::cout << (dynamic_cast<void*>(pod) == dynamic_cast<void*>(prvni) ? "pod == prvni" : "pod != prvni") << " && "
<< (dynamic_cast<void*>(pod) == dynamic_cast<void*>(druha) ? "pod == druha" : "pod != druha") << " && "
<< (dynamic_cast<void*>(prvni) == dynamic_cast<void*>(druha) ? "prvni == druha" : "prvni != druha") << std::endl;
delete pod; // Zde nemůžu použít delete prvni ani delete druha, protože ty třídy nemají virtuální destruktory
return 0;
}

vypíše:
Kód: [Vybrat]
0 = 0 = 0
pod == prvni && pod == druha
pod == prvni && pod == druha && prvni == druha
pod == prvni && pod == druha && prvni == druha
0x24c0010 = 0x24c0010 = 0x24c0018
pod == prvni && pod == druha
pod == prvni && pod != druha && prvni != druha
pod == prvni && pod == druha && prvni == druha

Za povšimnutí stojí to, že prosté pod == druha před porovnáním implicitně převede oba ukazatele na ukazatele na společného předka, což je DruhaNadTrida*, proto jejich porovnání vrátí true, i když hodnoty v pod a druha jsou různé. To je také důvod, proč nejde udělat prvni == druha: tyhle dvě třídy nemají společného předka.

webhope

Re:Přetypování pomocí dynamic_cast
« Odpověď #8 kdy: 19. 11. 2013, 23:53:05 »
Sten:
Já se ještě nedostal k tomu, abych to vyzkoušel. A teď na to nemám čas. Ale dostanu se k tomu, nebojte.

webhope

Re:Přetypování pomocí dynamic_cast
« Odpověď #9 kdy: 24. 11. 2013, 16:43:55 »
Dnes jsem se znovu vrátil k tomu dynamic_cast a uvědomil jsem si, že nerozumím jedné části
http://paste.ofcode.org/ETeDybvUfd8kWqYs5CjZAL

Je to ta část:
Kód: [Vybrat]
    Potomek &ref = dynamic_cast<Potomek&>(*po);
    cout << "Přetypováno" << endl;
    ref = dynamic_cast<Potomek&>(*su); // Bude vyvolána výjimka.
    cout << "Přetypováno" << endl;

Nějak nechápu jak to tam funguje nebo o co tu jde. Vypadá to, že nejdříve je tam pokus přetypovat předka na potomka, který selže a tak v bloku try {se provádí nějaké přetypování} ale fakt nevím o co tu jde?

Toto je dle článku "Přetypování v C++". Jinak vše ostatní chápu a to i včetně "Problémy s typy při vícenásobné dědičnosti".

DK

Re:Přetypování pomocí dynamic_cast
« Odpověď #10 kdy: 24. 11. 2013, 17:32:39 »
potomka muzes pretypovat na predka, protoze vis, ze potomek bude mit vzdycky stejne metody a promenne, jako predek, zatimco predka na potomka nemuzes prestypovat, protoze tam ty metody / promenne proste byt nemusi

v praxi je to stejne, jako kdybys rekl, ze zena je clovek (coz plati) a pak rekl, ze nejaky clovek je zena (neplati, clovek muze byt i muz)

webhope

Re:Přetypování pomocí dynamic_cast
« Odpověď #11 kdy: 24. 11. 2013, 17:49:45 »
DK:
Znamená to, že v tom bloku try se to přeruší? První dva řádky v bloku try jsou OK, takže to vypíše Přetypováno. Třetí řádek je problémový, proto je blok try přerušen?

Sten

Re:Přetypování pomocí dynamic_cast
« Odpověď #12 kdy: 24. 11. 2013, 17:50:58 »
Dnes jsem se znovu vrátil k tomu dynamic_cast a uvědomil jsem si, že nerozumím jedné části
http://paste.ofcode.org/ETeDybvUfd8kWqYs5CjZAL

Je to ta část:
Kód: [Vybrat]
    Potomek &ref = dynamic_cast<Potomek&>(*po);
    cout << "Přetypováno" << endl;
    ref = dynamic_cast<Potomek&>(*su); // Bude vyvolána výjimka.
    cout << "Přetypováno" << endl;

Nějak nechápu jak to tam funguje nebo o co tu jde. Vypadá to, že nejdříve je tam pokus přetypovat předka na potomka, který selže a tak v bloku try {se provádí nějaké přetypování} ale fakt nevím o co tu jde?

Toto je dle článku "Přetypování v C++". Jinak vše ostatní chápu a to i včetně "Problémy s typy při vícenásobné dědičnosti".

Ano, to je přesně ten problém. Ten ukazatel su ukazuje na instanci SuperClass. Ta ale nejde přetypovat na Potomek, protože není instancí té třídy ani jejího potomka, a proto ani neobsahuje proměnnou B. Tahle ukázka pak předvádí rozdíl mezi tím, když selže dynamic_cast<Type*>, což vrací nullptr (resp. zde se používá starší Céčkový NULL), a dynamic_cast<Type&>, jenž vyhazuje výjimku std::bad_cast.

Sten

Re:Přetypování pomocí dynamic_cast
« Odpověď #13 kdy: 24. 11. 2013, 17:54:02 »
DK:
Znamená to, že v tom bloku try se to přeruší? První dva řádky v bloku try jsou OK, takže to vypíše Přetypováno. Třetí řádek je problémový, proto je blok try přerušen?

Ano, jde o zpracování výjimky. Blok try se přeruší a pokračuje se od nejbližšího vhodného bloku catch. Pokud žádný takový blok není, vyvolá se std::terminate, který ve výchozím nastavení program zabije.