È un buon metodo per creare gerarchie di classi?

3

È un buon progetto memorizzare il tipo di oggetto nella classe base come enum? Ad esempio, considera la seguente gerarchia

Expr
--Unary 
--Binary
--Const
----Int
----Float

Come rappresentare la gerarchia di cui sopra in C ++, in modo da poter esaminare il tipo di oggetto di una sottoclasse.

enum ExprType {
   Const, Binary, Unary
};

class Expr {
   ExprType type;
public:
   Expr(ExprType t) : type(t) { }
   ExprType GetType() { return type; }
};

class BinNode: public Expr {
   Expr* left, *right;
   Operator op;
public:
   BinNode() : Expr(ExprType::Binary) { }
};

class UnrNode: public Expr {
   Expr* operand;
   Operator op;
public:
   UnrNode() : Expr(ExprType::Unary) { }
};

enum ConstNodeType {
   Int, Float
};

class ConstNode: public Expr {
   ConstNodeType cntype;
public:
   ConstNode(ConstNodeType t) : Expr(ExprType::Const), cntype(t) { }
   ConstNodeType GetConstType() { return cntype; }
};

class IntNode: public ConstNode {
   int value;
public:
   IntNode() : ConstNode(ConstNodeType::Int) { }
};

class FloatNode: public ConstNode {
   float value;
public:
   FloatNode() : ConstNode(ConstNodeType::Float) { }
};

Ora posso fare,

Expr* node = new IntNode();
node->GetType(); // returns ExprType::Const
node->GetConstType(); // return ConstNodeType::Int

Questo è un buon modo idiomatico per creare una gerarchia di classi? Praticamente voglio controllare il tipo di un oggetto di qualsiasi sottoclasse di Expr .

Posso farlo in C # usando l'operatore is senza un membro di tipo speciale.

Expr node = new IntNode();
assert(node is ConstNode);
assert(node is IntNode);

In C ++, posso usare RTTI, ma è questo il modo idiomatico di creare una classe di gerarchie? Esiste un modo alternativo per esprimere la stessa gerarchia?

    
posta Fish 19.05.2015 - 16:09
fonte

3 risposte

8

Ogni volta che senti il bisogno di ispezionare il tipo dinamico dei tuoi oggetti polimorfici in fase di esecuzione, dovresti mettere in discussione il tuo design. Questo è vero per qualsiasi linguaggio orientato agli oggetti che conosca. Il modello di visitatore può essere di grande aiuto per evitare di preoccuparsi del tipo dinamico di un oggetto .

Alcune persone sembrano pensare che barare attorno all'ispezione di tipo aggiungendo un tag a ciascun oggetto creerà un design migliore. Sono strongmente in disaccordo con queste persone. Se non puoi (o non vuoi) evitare l'ispezione del tipo di runtime, ammettilo e usa i meccanismi (presumibilmente più efficienti) per farlo, forniti a livello nativo dalla lingua. L'aggiunta di un tag di tipo aumenta il sovraccarico di run-time e generalmente rende il codice più complesso introducendo una nuova possibilità di aggiungere bug.

Data la seguente gerarchia

struct Base { virtual ~Base(); };
struct Derived : Base { void foo(); };

e un puntatore p su Base , il modo idiomatico per verificare se effettivamente punta a un oggetto Derived è provare un dymanic_cast in questo modo:

bool
is_this_a_derived(Base * p)
{
  return dynamic_cast<Derived *>(p);
}

Il dynamic_cast restituirà un puntatore Derived all'oggetto se è uno e un nullptr altrimenti. La conversione implicita in bool farà la cosa corretta, quindi.

Se lo desideri, puoi incapsularlo in un modello generico

template <typename BaseT, typename DerivedT>
bool
is_instance_of(const BaseT * p) noexcept
{
  return dynamic_cast<const DerivedT *>(p);
}

e usalo in questo modo. Anche se personalmente metterei in discussione l'utilità di questo.

void
example(Base * p)
{
  if (is_instance_of<Derived>(p))
    std::cout << "We have a derived thing here.\n";
  else
    std::cout << "This thing is not derived.\n";
}

Spesso, probabilmente vorrai fare qualcosa di specifico per la classe derivata. Ad esempio, richiama la sua funzione membro foo in questo esempio. Il modo idiomatico per farlo sarebbe il seguente:

void
invoke_foo_if_this_is_a_derived(Base * basep)
{
  if (auto derivedp = dynamic_cast<Derived *>(basep))
    derivedp->foo();
}

Il codice usa il fatto che posso limitare l'ambito di derivedp al corpo dell'istruzione if - l'unico posto in cui è utile e garantito non essere un nullptr .

Affinché questi esempi funzionino, è fondamentale che Base abbia almeno un membro virtual . Solitamente, vorrai rendere almeno il distruttore della classe base virtual perché altrimenti delete ing un oggetto derivato attraverso un puntatore di classe base invocherà un comportamento non definito. Il codice nella tua domanda non è quindi sicuro (a meno che tu non pianifichi di perdere tutto in ogni caso) perché Expr non ha virtual destructor.

    
risposta data 19.05.2015 - 20:38
fonte
1

No, non è un idioma. Se si deve assolutamente eseguire il cast dal tipo base al tipo derivato, utilizzare il meccanismo RTTI fornito dalla lingua. Ma prima , domanda perché vuoi farlo affatto!

Nella maggior parte dei casi non dovresti aver bisogno di preoccuparti del tipo di oggetto, dovresti chiamare le funzioni dei membri virtuali che il tipo derivato implementa per fare la cosa giusta per il suo tipo.

    
risposta data 19.05.2015 - 22:37
fonte
1

Va perfettamente bene, purché la gerarchia delle classi su cui stai facendo questo sia piccola, autonoma e non suscettibile di essere estesa da qualcuno che non è libero di refactoring.

Il modello di visitatore non è una cattiva idea, ma è una seccatura da implementare e, cosa più importante, ogni volta che provi a leggerlo, sei costretto a leggere un sacco di codice. Confrontate questo con la semplicità, la linearità e la pulizia del tutto in un solo luogo di attivare un enum, o chiamando i metodi "is_xyz ()" nella classe base.

Usi un modello di questo tipo per convenienza, non per mantenere felici i puristi.

C'era una domanda simile qui: Va bene avere oggetti che si lanciano da soli, anche se inquina l'API dei loro sottoclassi? su un meccanismo simile, e la mia risposta suggerisce che fare questo va bene con un punteggio di -5, (5 upvotes e 10 downvotes,) quindi sembra che il numero di persone che non sarebbe d'accordo con me è il doppio del numero di persone che sarebbero d'accordo. Tuttavia, sostengo la mia dichiarazione. Non ho 3, non 5, non 10, ma 30 anni di ininterrotta esperienza di programmazione a tempo pieno, e questa è la mia opinione, per quello che vale.

    
risposta data 20.05.2015 - 00:57
fonte

Leggi altre domande sui tag