Fórum Root.cz
Hlavní témata => Vývoj => Téma založeno: asd 19. 05. 2011, 15:49:33
-
Caute,
ako by som mal nasledujuci kod upravit aby fungoval?
class foo
{
public:
void a() {}
};
class bar : public foo
{
public:
void b() {}
};
int main()
{
foo * obj = new bar;
obj->a(); // ok
obj->b(); // error: 'class foo' has no member named 'b'
delete obj;
return 0;
}
cize co vlastne chcem spravit: mam premennu typu foo* do ktorej ulozim adresu na dynamicky alokovanu pamat triedy bar a chcem na tomto objekte pouzit funkciu b() ktora je definovana v triede bar ktora dedi od triedy foo.
Dakujem
-
objektove nejcistsi by asi bylo v tride A nadefinovat virtualni funkci b(). objekt pak pomoci tabulky virtualnich metod dynamicky zavola potrebnou metodu podle toho, na jakou tridu odkaz bude
-
danielsoft: ano, ale zabudol som povedat ze ono to mal byt kod (teda samozrejme nie tento :D) 3rd party kniznice a do toho sa mi nechce hrabat (keby ze tu kniznicu chcem updatovat tak zasa by som to musel prepisovat atd atd...)
tak som to vyriesil takto "spinavsie":
bar * obj2 = static_cast<bar*>(obj);
obj2->b();
-
static_cast je v tomto případě asi nejčištší workaround. Dá se použít v C++, pokud potomek nemá žádné členské proměnné ani virtuální funkce... Tedy ono se to dá použít i tak, ale volaná funkce nesmí pracovat s membery deklarované v potomkovi a při volání virtuálních metod nesmí člověk být překvapen, že se zavolá předek. Virtuální metody nově deklarované v potomkovi se volat nesmí.
Používám tenhle hack, pokud se potřebuju z nějakých důvodů dostat k proměnným deklarovaným "protected:" u knihoven třetích stran. Sám ve všech třídách používám výhradně deklaraci "protected:" abych si tuhle cestu rozšiřování v budoucnosti neuzavřel.
-
V tomto případě bych použil dynamic_cast - ten je přesně na toto dělaný. Za běhu zkontroluje typ (je tedy o trochu pomalejší než static_cast) a pokud by neseděl (tj. jeden objekt by nebyl potomkem druhého), vrátí NULL.
-
Mozes prosim priblizit blizsie ten problem? Celkom ma to zaujalo, najma preto ze si neviem predstavit normalnu situaciu kde nieco taketo nastane.....
-
danielsoft: ano, ale zabudol som povedat ze ono to mal byt kod (teda samozrejme nie tento :D) 3rd party kniznice a do toho sa mi nechce hrabat (keby ze tu kniznicu chcem updatovat tak zasa by som to musel prepisovat atd atd...)
tak som to vyriesil takto "spinavsie":
bar * obj2 = static_cast<bar*>(obj);
obj2->b();
Proč tak složitě? Stačí
((bar*)(obj))->b();
-
static_cast je bezpečnější. Nepovede se Ti omylem něco přetypovat mimo hierarchii. Zdá se, že jde o blbinu, ale stačí jedna taková chyba a půl dne ladění a člověk píše všude radši static_cast... :-)
-
Proč tak složitě? Stačí
((bar*)(obj))->b();
Protože to není totéž. static_cast umí zařídit i patřičný offset u vícenásobné dědičnosti:
V tomto případě bych použil dynamic_cast - ten je přesně na toto dělaný. Za běhu zkontroluje typ (je tedy o trochu pomalejší než static_cast) a pokud by neseděl (tj. jeden objekt by nebyl potomkem druhého), vrátí NULL.
dynamic_cast je dobré používat tam, kde opravdu nemám jistotu, že to co mi přilezlo ukazatelem je skutečně objekt, který očekávám. dynamic_cast má tyto nevýhody
- Lze použít pouze na objekty, které mají nějaké virtuální funkce (stačí destruktor)
- Nelze jej použít jako reinterpret_cast, tedy objekt opravdu musí být tím, co očekáváme (někdy se hodí umět to přetypovat navzdory)
- Vyžaduje akci v runtime. Zatímco všechny XXX_cast operátory vyhodnocuje překladač, dynamic_cast se vyhodnocuje za běhu. A není to zrovna levná záležitost.
Většinou se to řeší tak, že v tabulce virtuálních metod je přibalen ukazatel na záznam o třídě objektu a ten obsahuje odkaz na tabulku všech tříd, které dědí a to i tranzitivně. Operátor pak projde tabulku a nalezne v ní třídu ukazatele a cílovou třídu a spočítá transformaci výsledného pointeru... tedy spočítá jeho offset. Jako klíč do tabulky bývá zakódované jméno třídy (porovnávají se řetězce) - Microsoft, případně nějaké GUID třídy (COM+, prý to používá i gcc)
Nicméně, pokud vím, že mi do nějaké funkce může přilézt objekt jen určitého potomka k danému rozhraní, nebo základní třídy, tak používám static_cast, je to rychlejší. Je však dobré dynamic_cast ověřit aspoň pomocí _assertu v testovací verzi.
PS: Nevýhoda static_castu: nedá se použít na virtuální dědičnost.
-
Ondra: s tim C like castem je to IMHO jinak. Co vím, tak funguje jako "best cast": pokud jde static cast, udělá static cast, pokud nejde, udělá reinterpret cast. Takže offsety IMHO počítá také. Pokud bych chtěl bez počítání, musím to vzít reinterpretem, nebo C-like castem přes mezityp void.
http://www.cplusplus.com/forum/general/19300/
Nebo se překladače nechovaj dle normy?
-
Nebo se překladače nechovaj dle normy?
Nevím, upřímě normu neznám zpaměti a doposud jsem C style cast na pointer považoval automaticky za reinterpret_cast
-
Mimochodem, nevíte někdo náhodou, jestli C style cast na (void *) u polymorfních objektů uděla dynamic_cast<void *>()
Nebo bych potřeboval nějaký univerzální castovač na void *. Hodí se to, pokud chce člověk zjistit base adresu objektu. U polymorfních objektů lze právě použít dynamic_cast<void *> ale pokud se nejedná o polymorfní objekt, tak to selže na chybě překladu. Dosti komplikovaným způsobem přes několik desítek šablon lze udělat to, že poznám primitivní typ, poznam polymorfní typ a na základě toho zvolím castovač, ale je to opruz.
-
cast na void * myslím nic nepočítá.
Ale jestli to umíš udělat, tak to zabal do nějaký funkce a že to v tý knihovně šíli Ti může bejt jedno, ne? Nicméně tu šílenost bych rád viděl :-).
-
cast na void * myslím nic nepočítá.
Ale jestli to umíš udělat, tak to zabal do nějaký funkce a že to v tý knihovně šíli Ti může bejt jedno, ne? Nicméně tu šílenost bych rád viděl :-).
No to není věc knihovny, ale hlavičkových souborů. A to je horší. A v zásadě to vypadá tak, že se deklaruje šablona IsPrimitiveType<T>, které se lze zeptat, zda T je primitivní typ (třeba int, short), pak je tam šablona IsPointer<T> a IsReference<T>. Jakmile se vyloučí tyhle typy, pak se řeší, zda je IsPolymorphic<T>, ten se dělá tak, že se původní typ podědí a přidá se mu virtuální destruktor a měří se velikost výsledného objektu. Pokud se liší od objektu původního typu, znamená to, že původní typ nebyl polymorfní (přidáním virtuálního destruktoru se založila tabulka virtuálních adres). No a na konci téhle anabáze lze rozdělit cast na void na případ, kdy se udělá reinterpret_cast a dynamic_cast. Z toho jsem schopen zjistit adresu objektu, a offset mezi dodaným ukazatelem a skutečným začátkem objektu v paměti.
Vyhodnocení dělá překladač, ve výsledném kódu je pro polymorfní typy dynamic_cast a pro nepolymorfní typy nic (cast se nepřekládá). Vyhodnocení všech šablon dost protahuje překlad kódu.
-
Ondra: s tim C like castem je to IMHO jinak. Co vím, tak funguje jako "best cast": pokud jde static cast, udělá static cast, pokud nejde, udělá reinterpret cast. Takže offsety IMHO počítá také. Pokud bych chtěl bez počítání, musím to vzít reinterpretem, nebo C-like castem přes mezityp void.
http://www.cplusplus.com/forum/general/19300/
Nebo se překladače nechovaj dle normy?
problem s C-style pretypovanim je inde...kratky priklad
struct a {
...
};
struct b; //< forward declaration
main(...)
{
b* v0=(b*)new a(); // compiler prejde bez problemov...prijemnu zabavu s hladanim problemu
b* v1=static_cast<b*>(new a()); // compiler zahlasi chybu... 'static_cast' : cannot convert from 'a *' to 'b *'"
}
vysvetlovat preco si nechat C-style pretypovanie pre C asi uz nema vyznam...