È un anti-pattern per usare void * ed enum per abilitare il controllo dei caratteri?

2

In un motore di gioco, hai Object s composto da diversi componenti. Uno di questi è un RenderComponent che può essere un Mesh , un Sprite o un Light .

Ora, tutte queste classi sono molto diverse con interfacce non compatibili, quindi ricavarle tutte da RenderComponent ha poco senso. Inoltre, un Object può avere solo un RenderComponent alla volta, quindi puoi salvare tre puntatori in Object , due dei quali devono sempre essere nullptr , sembra uno spreco. Quindi quello che mi è venuto in mente è usare RenderComponent per memorizzare un puntatore void* e il tipo.

class RenderComponent{
public:
    enum class RenderType{
        Mesh, Sprite, Light
    };

    RenderType getType(){ return type_; }

    Mesh* asMesh(){ 
        if(type_ == RenderType::Mesh)
            return static_cast<Mesh*>(pointer_.get()) 
        else
            return nullptr; 
    } /* the same stuff for Sprite and Light */

    RenderComponent(unique_ptr<Mesh>&& pointer) :
        pointer_(std::move(pointer)),
        type_(RenderType::Mesh)
    {} /* the same stuff for Sprite and Light */

private:
    unique_ptr<void> pointer_;
    RenderType type_;
}

Dovrebbe funzionare come previsto, ma credo che questo sia un anti-modello. Sto praticamente scartando tutti gli aspetti tipografici del C ++ e poi li reimplemento da solo. Questo non può essere corretto.

Quindi, questo è un anti-pattern? Quali sono soluzioni alternative comuni per questo problema? Mi manca qualcosa di ovvio? O è una cosa conosciuta, ma (per una buona ragione) solo uno schema usato di rado?

    
posta iFreilicht 17.08.2014 - 18:21
fonte

2 risposte

6

Il modo corretto per avvicinarsi a questo è usare RTTI e dynamic_cast . Questo fa quasi la stessa cosa di quello che stai facendo, ma in un modo sicuro per la lingua, approvato dalla lingua. ( dynamic_cast genera se cerchi oggetti caso in cose a cui non sono correlati.)

Un motivo per cui le persone scrivono un codice come questo è che dynamic_cast è entrato nella lingua dopo che le persone lo hanno usato per un po '. Troppo spesso, gli sviluppatori non tengono il passo con i miglioramenti del linguaggio e continuano a fare le cose nel modo in cui sono abituati. Quindi nel 1994, questo potrebbe essere stato un codice perfettamente buono.

L'altro motivo per cui le persone scrivono un codice come questo è dovuto alla convinzione diffusa che RTTI danneggi le prestazioni. Il pensiero è che, poiché RTTI deve memorizzare informazioni di tipo per ogni singola classe, "spreca memoria". Vedi spesso questo atteggiamento con persone che sviluppano embedded o sviluppano giochi. Inoltre, dynamic_cast richiede la gestione delle eccezioni e gli stessi gruppi spesso disattivano questa gestione delle eccezioni.

C'è qualcosa dietro a questo in quanto RTTI può aumentare il tuo impatto sulla memoria. Ciò si verifica in particolare quando si fa un uso intensivo di classi basate su modelli come STL, in quanto ciò può far sì che il compilatore crei un numero elevato di classi sotto le copertine, che ottengono tutte informazioni sul tipo. Su un progetto su cui ho lavorato in questo modo, abbiamo visto quasi mezzo megabyte perso per digitare le informazioni. Il mio sentimento, tuttavia, è che se hai un budget limitato di memoria, potresti essere meglio refactoring delle classi basate su modelli in qualcosa che non usa i template.

A causa della generale avversione di RTTI nell'industria dei giochi, i motori di terze parti non lo useranno quasi mai. Si tratta più di fornire ciò che il cliente richiede piuttosto che una buona progettazione.

Tutto ciò richiede un albero ereditario, ovviamente. Tu dici:

Now, all these are vastly different classes with non-complatible interfaces, so deriving them all from RenderComponent makes little sense.

Ma trovo questo discutibile, perché la tua prima riga è:

One of these is a RenderComponent which can be either a Mesh, a Sprite or a Light.

Enfasi mia. "be" sembra denotare una relazione "is-a", che è un classico segno di una gerarchia di classi.

Non penso che questo sia necessariamente un "anti-pattern", tuttavia, poiché questo è un modo in cui molti progetti di successo hanno affrontato il problema, ed è qualcosa che è finito per essere codificato nella lingua.

    
risposta data 17.08.2014 - 18:45
fonte
1

Questo è anti-OO , ma il design del sistema di componenti di entità è espressamente non orientato agli oggetti. Non penso che questa sia una cattiva pratica in generale per software basato su CES, ma nel tuo esempio è una cattiva pratica. A mio parere, la funzione di rendering dovrebbe probabilmente provare e ottenere una sorta di MeshComponent da Entity e renderlo. Se il componente non è presente, restituisci nullptr . Per diversi tipi di rendering è possibile avere anche i rendercomponents derivati. O diversi sistemi di rendering, che potrebbero essere un approccio ancora più chiaro.

    
risposta data 17.08.2014 - 18:44
fonte

Leggi altre domande sui tag