.[ ČeskéHry.cz ].
Double dispatch

 
odeslat nové téma   Odpovědět na téma    Obsah fóra České-Hry.cz -> C / C++
Zobrazit předchozí téma :: Zobrazit následující téma  
Autor Zpráva
Solid.Sn



Založen: 08. 08. 2009
Příspěvky: 55

PříspěvekZaslal: 1. únor 2012, 20:06:49    Předmět: Double dispatch Odpovědět s citátem

Ahoj,

chtěl bych se zeptat, zda jde nějak vyřešit následující problém. Jde o to že mám objekty (myšlěno herní objekty), které reagují na zprávy a šlo mi o to, aby šlo jednoduše pomocí dědičnosti rozšiřovat zprávy i objekty o další. V případě, že chci přidat nějakou zprávu, prostě vytvořím pomocí dědičnosti novou třídu a pokud chci, aby objekt na zprávu reagoval, vytvořím ve třídě objektu metodu TranslateMessage, která nese jako parametr typ nově vytvořené zprávy (klasické přetížení). Objekt tak může reagovat na novou zprávu a v případě, že metodu u objektu pro novou zprávu neimplementuju se prostě volá metoda s parametrem kořenového předka (MESSAGE * v tomto případě).

Každá zpráva musí mít implementovanou metodu Send, která zajistí správné zavolání metody TranslateMessage u herních objektů.

To je ale jen část toho, čeho jsem chtěl dosáhnout. Chtěl jsem totiž přidávat i herní objekty pomocí dědičnosti (z třídy ELEMENT). Jakmile ale třídu pro objekt vytvořím a rozšířím ji například o příjem zpráv, kterým původní předek nerozumněl, tyto zprávy zpracovat neumí - rozumí pouze těm, kterým předek, což je logické, vzhledem k tomu jak se volají virtuální metody. Je možné to vyřešit jinak, než abych u každého herního objektu měl metodu, která by prostě parametr (ukazatel na zprávu) zkusila pomocí dynamic_cast přetypovat postupně na všechny zprávy, kterým má objekt rozumnět a pokud se to povede, tak na tuto zprávu reagovat tak jak má.


kód:
class ELEMENT;

class MESSAGE
{
   public:
      virtual void Send( ELEMENT * Element ) = 0;
};
class MESSAGE1 : public MESSAGE
{   
   public:
      virtual void Send( ELEMENT * Element );
};
class MESSAGE2 : public MESSAGE
{   
   public:
      virtual void Send( ELEMENT * Element );
};
class ELEMENT
{
   public:
      ELEMENT(){};   
      virtual void TranslateMessage( MESSAGE1 * Message )
      {
         std::cout << "MESSAGE1";
      }
      virtual void TranslateMessage( MESSAGE2 * Message )
      {
         std::cout << "MESSAGE2";
      }
      virtual void TranslateMessage( MESSAGE * Message )
      {
         std::cout << "UnknownMESSAGE";
      }
};
void MESSAGE1 :: Send( ELEMENT * Element )
{
   Element -> TranslateMessage( this );
}
void MESSAGE2 :: Send( ELEMENT * Element )
{
   Element -> TranslateMessage( this );
}
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu
Houp



Založen: 28. 07. 2007
Příspěvky: 672

PříspěvekZaslal: 1. únor 2012, 20:48:05    Předmět: Odpovědět s citátem

Nějak mi přijde, že takto úplně zabíjíš výhodu objektového přístupu od obyčejného switche, který by dle mě zde fungoval zcela stejně.

Přijde mi velice nežádoucí, abys musel s novým druhem zprávy upravovat i třídu Element.

Zkus trochu víc popsat doménu, které se to týká. Jaké zprávy a jaké reakce na ně si představuješ?
_________________
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu Zobrazit autorovi WWW stránky
Tringi



Založen: 28. 07. 2007
Příspěvky: 290

PříspěvekZaslal: 1. únor 2012, 22:04:29    Předmět: Odpovědět s citátem

CRTP FTW?
kód:
#include <cstdlib>
#include <cstdio>

class MessageBase {
    public:
        virtual ~MessageBase () {};
};

template <typename Derived>
class Message : public MessageBase {
    protected:
        void Translate (const Derived * d) { return d->Work (this); };
};

class Message1;
class Message2;

class Element {
    public:
        virtual void Work (const MessageBase &) { printf ("E::W::M\n"); };
        virtual ~Element () {};
};

class Element1 : public Element {
    public:
        using Element::Work;
        virtual void Work (const Message1 &) { printf ("E::W::M1\n"); };
};
class Element2 : public Element1 {
    public:
        using Element1::Work;
        virtual void Work (const Message2 &) { printf ("E::W::M2\n"); };
};

class Message1 : public Message <Message1> {
    /* ... */
};
class Message2 : public Message <Message1> {
    /* ... */
};

int main () {
    Element e;
    Element1 e1;
    Element2 e2;

    e1.Work (Message1 ());
    e1.Work (Message2 ());

    std::system ("pause");
    return EXIT_SUCCESS;
};

_________________
WWW | GitHub | TW
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu Zobrazit autorovi WWW stránky
Solid.Sn



Založen: 08. 08. 2009
Příspěvky: 55

PříspěvekZaslal: 1. únor 2012, 22:54:10    Předmět: Odpovědět s citátem

Tringi napsal:
CRTP FTW?...


Zběžně jsem na to koukal a zdá se, že mi to pomůže. Moc děkuji za nasměrování. Smile

EDIT:

Tak jsem přišel na to, že je to vlastně hodně podobné tomu co jsem zkoušel dřív, ale stále to nefunguje tak jak jsem si představoval. Confused
citace:

Element * e1a = new Element1();
Element1 * e1b = new Element1();

e1a -> Work( Message1() ); // nefunguje, jelikož typ není Element1 * ale Element *, volá se tedy metoda Element::Work, ne Element1::Work (a to je přesně to, čeho jsem chtěl dosáhnout, tedy zavolat Work z Element1, v případě, že je to instance Element1 i pokud mám ukazatel pouze Element*, jde tam o to přetížení...).

e1b -> Work( Message1() ); // funguje tak jak má, typ je Element1 * ...
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu
Solid.Sn



Založen: 08. 08. 2009
Příspěvky: 55

PříspěvekZaslal: 3. únor 2012, 11:24:13    Předmět: Odpovědět s citátem

Tady jsem našel popis dvou způsobů řešení (ale ani jeden se mi moc nelíbí Sad ). První je postupné zkoušení přetypovat zprávu pomocí dynamic_cast na ten typ zpráv, kterým daný objekt má rozumět (to je i to co jsem psal v prvním příspěvku) a druhý způsob používá typeinfo.

Obě metody mi příjdou tak trochu proti OOP, ale nic lepšího jsem nevymyslel. Smile

http://www.gamedev.net/page/resources/_/technical/game-programming/effective-event-handling-in-c-r2459


Naposledy upravil Solid.Sn dne 3. únor 2012, 11:44:28, celkově upraveno 1 krát
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu
Tringi



Založen: 28. 07. 2007
Příspěvky: 290

PříspěvekZaslal: 3. únor 2012, 11:36:25    Předmět: Odpovědět s citátem

Aplikovat standardní vzor visitor, kterým se typicky takové hierarchie řeší, ale není to nejtriviálnější ...nebo zjednodušit design.

Další variantou může být použít polymorfismus statický, tam se ale rozloučíš s rychlou kompilací, nebo vlastní něco-jako 2D virtuální dispatch, třeba opět pomocí statické mapy templejtek. Všechno je rychlejší než RTTI (dynamic_cast a typeinfo, je to to samé), volání funkce přes pointer bývá při správné konstelaci všech cache rychlejší než if.
_________________
WWW | GitHub | TW
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu Zobrazit autorovi WWW stránky
Marek



Založen: 28. 07. 2007
Příspěvky: 1782
Bydliště: Velká Morava

PříspěvekZaslal: 3. únor 2012, 14:40:15    Předmět: Odpovědět s citátem

Celkem mi přijde, že se snažíte aplikovat OOP tam, kde se nehodí. Základní a zároveň největší výhoda OOP je zapouzdření (dat nebo funkcionality). OOP není a nikdy nebylo o tom, že použijete slovo "class". V tvém případě není co zapouzdřovat, jelikož potřebuješ, aby tvoje objekty rozuměly všem typům zpráv (jinak si s nimi nemají jak poradit). Na to se OOP vůbec nehodí, resp. ne na řešení tohoto konkrétního problému.

Normálně použij switch přes všechny typy zpráv. Pro typy zpráv si udělej třeba enum a přes něj je budeš identifikovat.

Až budeš potřebovat skrýt konkrétní funkcionalitu za nějaký obecný interface, pak využij, co ti OOP poskytuje, protože zrovna tam se vyplatí.
_________________
AMD Open Source Graphics Driver Developer
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu
Al



Založen: 23. 10. 2007
Příspěvky: 196

PříspěvekZaslal: 9. únor 2012, 01:38:35    Předmět: Odpovědět s citátem

Jako obvykle, nelze než souhlasit s Eosie/Markem. Neřekl bych přímo, že OOP se na to nehodí, spíš mi přijde, že je tu ostrý nepoměr práce/odměna. Jakože naprogramujete tuny zdrojáku, než tam nasekáte ty tisíce tříd a všechno sofistikovaně podědíte, ale jako výsledek získáte pidimalinkatou funkcionalitu. Tak tímto způsobem bych OOP teda rozhodně nepoužíval. Ve hrách už vůbec ne.

Z globálního pohledu by se na to OOP jistě hodilo, ale je potřeba se na to dívat víc high-level. Nepředstavovat si OOP jen jako nástroj na low-level rozsekání kódu do tisíce tříd. Zvlášť v C++ jsou někdy ty OOP zdrojáky dost těžkopádné, je to ostatně z podstaty low-level jazyk, což je někdy na škodu.
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu
Zobrazit příspěvky z předchozích:   
odeslat nové téma   Odpovědět na téma    Obsah fóra České-Hry.cz -> C / C++ Časy uváděny v GMT + 1 hodina
Strana 1 z 1

 
Přejdi na:  
Nemůžete odesílat nové téma do tohoto fóra
Nemůžete odpovídat na témata v tomto fóru
Nemůžete upravovat své příspěvky v tomto fóru
Nemůžete mazat své příspěvky v tomto fóru
Nemůžete hlasovat v tomto fóru


Powered by phpBB © 2001, 2005 phpBB Group


Vzhled udelal powermac
Styl "vykraden" z phpBB stylu MonkiDream - upraveno by rezna