Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

[C++] Dziedziczenie w grach, a dynamic_cast

Ostatnio zmodyfikowano 2012-03-25 15:43
Autor Wiadomość
DejaVu
Temat założony przez niniejszego użytkownika
[C++] Dziedziczenie w grach, a dynamic_cast
» 2012-03-25 12:58:24
Rzutowanie za pomocą dynamic_cast nie zawsze może działać, więc można ten problem ominąć następująco:
C/C++
class IKlasa
{
public:
    enum TypE { E_GRACZ, E_BOT }
    virtual getType() = 0;
}; //class IKlasa

class CGracz
    : public IKlasa
{
public:
    virtual getType() { return E_GRACZ; }
   
    static CGracz * cast( IKlasa * pKlasa )
    {
        if( !pKlasa )
             return NULL;
       
        if( pKlasa->getType() != E_GRACZ )
             return null;
       
        return reinterpret_cast < CGracz *>( pKlasa );
    }
   
}; //class CGracz
Korzystanie z klasy CGracz będzie wyglądało tak:
C/C++
IKlasa * pKlasa = new CGracz();
//...
CGracz * pGracz = CGracz::cast( pKlasa );
if( pGracz )
{
    //... na 100% poprawny obiekt
} //if

/edit:
Później można wykonać coś takiego:
C/C++
void executeGracz( CGracz * pGracz )
{
    //tu obsługa gracza
}

//...

BOOST_FOREACH( IKlasa * pItem, vKontenerElementow )
{
    if( !pItem )
         continue;
   
    switch( pItem->getType() )
    {
    case IKlasa::E_GRACZ:
          executeGracz( CGracz::cast( pItem ) );
        break;
       
        //... tu dopisać obsługę pozostałych zadań
    } //switch
} //BOOST_FOREACH
P-53181
npHard
» 2012-03-25 14:28:02
Warto dodać, że często korzystanie z dynamic_cast lub jakiegoś obejścia jak wyżej oznacza, że mamy design smell i powinniśmy się zastanowić, czy nie da się tego lepiej zamodelować. W tym przypadku dodanie jakiejkolwiek klasy do hierarchii wymaga:
1. edycji IKlasa
2. edycji wszystkich miejsc w których sprawdzany jest typ obiektu]
W tym przypadku lepiej zrobić tak:
C/C++
class IKlasa {
public:
    virtual void Execute() = 0;
};

class CGracz
    : public IKlasa
{
public:
    void Execute() {
        //tu obsługa gracza
    }
};
BOOST_FOREACH( IKlasa * pItem, vKontenerElementow ) {
    if( pItem ) {
        pItem->Execute();
    }
}

A jeśli mamy ustabilizowaną hierarchię klas a chcemy łatwo dodawać do niej nowe zachowania to moim zdaniem lepiej skorzystać ze wzorca projektowego Visitor( o ile nie pomyliłem nazwy).
P-53185
pekfos
» 2012-03-25 15:11:23
1. edycji IKlasa
2. edycji wszystkich miejsc w których sprawdzany jest typ obiektu]
1. Tylko typu wyliczeniowego. We wzorcu Visitor musiałbyś dodać nowa metodę dla każdego nowego typu.
2. Czyli np wadą zmiany deklaracji funkcji jest konieczność zmienienia wszystkich wywołań?
P-53186
DejaVu
Temat założony przez niniejszego użytkownika
» 2012-03-25 15:13:48
Tak, ale zazwyczaj jak robisz metodę wirtualną Execute to ona nie przyjmuje żadnych argumentów, a obiekt sam z siebie ma dostęp w metodzie Execute do wszystkich niezbędnych narzędzi (czyli np. innych obiektów).

/edit:
Przykład:
C/C++
class IKlasa
{
    virtual void Execute() = 0;
};

class CKlasa
    : public IKlasa
{
    CKlasa( CDane & dane )
        : m_dane( dane )
    { }
    virtual void Execute()
    {
        m_dane.costam();
    }
private:
    CDane & m_dane;
};

//...
CDane dane;
//...
IKlasa * pKlasa = new CKlasa( dane );
pKlasa->Execute();
P-53187
npHard
» 2012-03-25 15:43:43
1. Tylko typu wyliczeniowego. We wzorcu Visitor musiałbyś dodać nowa metodę dla każdego nowego typu.
Tak, ale jak mówiłem visitor jest dla stałych hierarchii, do których rzadko dodaje się nowe klasy. Poza tym w visitorze można stworzyć metodę typu catch-all, która będzie przyjmowała wskaźnik na obiekt typu IKlasa.
2. Czyli np wadą zmiany deklaracji funkcji jest konieczność zmienienia wszystkich wywołań?
Oczywiście, że tak. Wyobraź sobie na prawdę duży projekt, w którym dana funkcja wywoływana jest 1000 razy. Zmieniamy deklarację i... mamy problem. Można oczywiście zrobić jakąś wyszukaną operację typu "przeszukaj wszystko i zamień", ale to też jest obarczone sporym prawdopodobieństwem błędów. W takim wypadku lepiej jest zrobić nową funkcję z nowym zestawem argumentów, a starą zmienić tak żeby tylko wywoływała tę nową... Kod należy pisać i projektować tak żeby jakaś nie wielka zmiana nie pociągała za sobą konieczności przejrzenia połowy projektu.

I nie powiedziałem, że dynamic_cast albo to rozwiązanie z enumem i GetType() jest złe. Po prostu nie powinno się go nadużywać. Wszystko jest dla ludzi.
P-53191
« 1 »
  Strona 1 z 1