C++ oraz instanceof

Jak wszyscy wiemy, Java jest językiem programowania, który nie leżał nawet koło języków tak "niskopoziomowych" jak C czy C++. Dzięki temu zawiera pewne niesamowite konstrukcje. No… niesamowite są dopóki człowiek się do nich nie przyzwyczai…

Jedną z takich konstrukcji jest tytułowe instanceof. Użycie tego słowa kluczowego pozwala na sprawdzenie czy obiekt jest instancją danej klasy. Użycie tego wynalazku wygląda w Javie tak:

if(foo instanceof Bar) {
/* ... */
}

Jest to ogromna wygoda, a jak wiemy – do wygód człowiek przyzwyczaja się szybko. Dlatego ubolewałem jakiś czas nad brakiem możliwości sprawdzenia czy dany obiekt jest instancją jakiejś klasy w C++…

Zaraz, zaraz. Nie da się? Dziś poczytując blog Xion’a wpadłem na szatański plan zmuszenia C++ do wykrycia klasy obiektu. Jak się okazało mechanizm działa nawet "lepiej" niż w Javie 😉 Najpierw zaprezentuję kod:

class Foo {};
class Bar : public Foo {};
// ...

Bar bar;
try { throw bar; }
catch(Foo f) { cout << "Obiekt jest klasy foo!" << endl; }

Jak widać po powyższym kawałku kodu – możemy wykryć klasę obiektu nawet jeśli jest to pochodna jakiejś klasy bazowej!

Ale to jeszcze nie wszystko. Mówiłem, że to nawet lepsze niż instanceof. Tak na prawdę możemy użyć tego mechanizmu jako swego rodzaju switch dla wykrywania klasy obiektu – nawet jeśli klasy które sprawdzamy są całkowicie ze sobą niezwiązane:

class Foo{};
class Bar{}; // brak zależności pomiędzy klasami!
//...
Bar bar;
try { throw bar; }
catch(Foo f) { cout << "Foo" << endl; }
catch(Bar b) { cout << "Bar" << endl; }

Wszystko za sprawą niekonfliktowości kompilatorów C++. W Javie niestety taki numer nie przejdzie z dwóch powodów. Po pierwsze – nie można rzucać wszystkimi obiektami (możemy rzucać tylko klasy implementujące interfejs Throwable). Po drugie – nie możemy łapać wyjątków, których nie rzucamy w sekcji try.

Jak się okazuje czytanie blogów zamiast uczenia się do kolokwium może przynieść trochę pożytku 😉

Reklamy

10 myśli nt. „C++ oraz instanceof

  1. > Po drugie – nie możemy łapać wyjątków, których nie rzucamy w sekcji try.
    Chodzi o to, że nie wolno rzucać wyjątków poza sekcją try? Jeśli tak to wystarczy oznaczyć metodę jako rzucającą wyjątek.

    Zaciekawiłeś mnie tekstem na tyle, że poszukałem trochę więcej informacji na temat RTTI w C++ i w sumie można zrobić to bardziej elegancko, właśnie dzięki RTTI (operator typeinfo), drugi sposób to standardowy operator dynamic_cast tylko wtedy musimy mieć co najmniej jedną metodę wirtualną.

    • > Chodzi o to, że nie wolno rzucać wyjątków poza sekcją try?
      Nie, chodziło mi o to, że jak dany kawałek kodu nie łapie generuje danego wyjątku to robienie catcha dla niego wywali błąd. Jest to jednak nieprawda. Sprawdziłem przed chwilą i jednak się da – miałem takie przekonanie, bo Netbeans zawsze jęczy na takie rzeczy.

  2. Ten sposób jest jednak mało przydatny do dynamicznego rozpoznawania typów, wyjątki nie wykorzystują RTTI i tak naprawdę informacja o typie jest ustawiana w trakcie rzucenia wyjątkiem na podstawie tego co rzucisz (a przynajmniej w gcc). 🙂

    No i małe sprostowanie, typeinfo to nie operator tylko klasa.

    • Nikogo nie będę przekonywał, że trick z try..catchem jest lepszy od narzędzia dedykowanego do badania typów 😉

      Nie wiem jednak, dlaczego uważasz że jest mało przydatny. Jeśli rozumieć go jako takiego switcha dla typów to robi dokładnie to co powinien – bada typ obiektu i w zależności od niego wykonuje jeden z kawałków kodu.

      • Nie uważam, że jest nieprzydatny, uważam, że jest praktycznie bezużyteczny. 🙂

        Np. w ten sposób nie rozpoznasz czy pod wskaźnikiem typu void siedzi jakiś obiekt, zostanie rzucony wyjątek o typie void* i taki catch musisz uwzględnić. Z drugiej strony C++ jest językiem o bardzo silnej typizacji, więc jeśli posiadasz obiekt danego typu, masz pewność, że jest tego typu, możliwe oczywiście jest mataczenie poprzez rzutowanie, ale tutaj tylko RTTI może pomóc.

        Jedyny pomysł jaki przychodzi mi do głowy, w którym może się to przydać to makrodefinicje, której zawartość „określa” typ (określanie tego w ten sposób to małe nadużycie bo makra preprocesora nie są językiem samym w sobie, ale…). Wtedy można wykorzystać wyjątki do wykrycia typu bez zaprzęgania do tego bezpośrednio RTTI (zależy od kompilatora i implementacji wyjątków, nie wiem czy też nie od systemu).

        Co do klas dziedziczących po sobie i ich rozpoznawania, lepiej skorzystać z dynamic_cast (jest prostszy). Co prawda wymaga choćby jednej metody wirtualnej, ale jeśli mamy zamiar tworzyć wskaźniki klasy bazowej na klasy pochodne powinniśmy stosować np. wirtualny destruktor, choćby dla własnego bezpieczeństwa, jeśli nam szkoda 4B na wskaźnik na vtable to niestety trzeba samemu zaimplementować rozpoznawanie typów, a wyjątki nie rozróżnią typów dziedziczących po sobie (jaki typ rzucisz taki dostaniesz w bloku catch, no chyba, że wyjątki w danej implementacji korzystają z pełnego RTTI – na pewno tak nie działa w gcc 4.3.4, ponoć w starszych wersjach wyjątki nie działały z flagą -fno-rtti więc być może).

        Nie mniej wpis i tak godny uwagi, bo zwrócił uwagę na ważny aspekt, i może ktoś podsunie pomysł na ładną implementację rozpoznawania typów z wykorzystaniem jakiegoś innego mechanizmu.

      • No tak, no tak… Masz rację. Nie pomyślałem o wskaźniku typu void* 🙂
        Faktycznie sposób jest słabo użyteczny 😉

  3. Ekhm.. Właśnie po to wymyślono metody wirtualne, żeby nie musieć robić już switch(typ_obiektu). Nie promuj takiego kodowania, z łaski swojej ;P (Już nie wspomnę o takim, jakim koszmarnych ciachem wydajnościowym jest użycie do tego wyjątków…).

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Wyloguj / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Wyloguj / Zmień )

Connecting to %s