2012-03-28 16 views
7

Ho sempre sentito che non si dovrebbe ereditare da una classe senza distruttori virtuali e non ho prestato molta attenzione perché non uso l'ereditarietà tutto questo spesso. Questa regola si applica anche se non si desidera utilizzare il polimorfismo, ma si desidera semplicemente tutte le funzionalità di una classe e si desidera aggiungerne altre? Per essere concreti, la classe seguente sarebbe sicura, con un comportamento ben definito, a patto che non la usassi polimorficamente? (vale a dire senza puntatori Eliminazione di base a oggetti derivati)Eredita da classi senza distruttori virtuali

template<typename T> 
class SomewhatSafeVector : public std::vector<T> 
{ 
public: 
    typedef std::vector<T> base; 

    T& operator[](unsigned n) { 
     if (n >= base::size()) 
     { 
      throw IndexOutOfBounds(); 
     } 
     return base::operator[](n); 
    } 
}; 
+1

Non importa se è OK o no, ma non si dovrebbe ancora derivare da contenitori di libreria standard. Inoltre, se hai problemi ad accedere a contenitori dinamici all'interno dei loro limiti, potresti preferire guardare il tuo pensiero algoritmico di grandi dimensioni (pensa "0-1-molti" e "intervalli"), dal momento che un accesso non vincolato è di solito un errore * logico *. –

+4

Penso che nel tuo particolare esempio, l'ereditarietà non è una soluzione terribilmente elegante poiché l'ereditarietà è destinata al riutilizzo delle interfacce, non al riutilizzo dell'implementazione. È chiaro che non riutilizzate l'interfaccia, dato che il vostro operatore '[] 'genera un'eccezione che non' std :: vector'. Se si desidera riutilizzare il codice, basta usare semplici funzioni condivise o (come in questo caso), rendere 'std :: vector' un membro di' SomewhatSafeVector'. –

+0

@KerrekSB: Al primo, perché no? Per il secondo, non ho problemi. Ma penso che i contenitori controllati dai limiti sarebbero una buona idea per scopi didattici e di debug. –

risposta

6

Ho sempre sentito dire che non si dovrebbe ereditare da una classe senza distruttori virtuali

Questa è una regola empirica dato a principianti, perché che spiegano tutte le complessità prendono troppo tempo ed è solo più sicuro (e non così costoso per i programmi di esercizi) per dare loro solo poche linee di base che funzionano tutte le volte (anche se può essere eccessivo).

È possibile utilizzare perfettamente l'ereditarietà senza un distruttore virtual nella classe base. D'altra parte, se la classe base non ha affatto il metodo virtual, l'ereditarietà è probabilmente lo strumento sbagliato per il lavoro. Per esempio: nel tuo caso se uso SafeVector<T> sv; sv[3]; allora è sicuro, se lo faccio std::vector<T>& v = sv; v[3]; non lo è ... questo è perché siete semplicemente nasconde il metodo della classe base, non ignorando che (gomito in sul vostro avvertimento livello, ti faranno sapere).

Il modo corretto sarebbe utilizzare la composizione e quindi creare metodi di inoltro al membro dell'implementazione per i metodi effettivamente utilizzati. In pratica, diventa faticoso perché C++ non supporta la delega (using attribute.insert;) così molti ricorrono all'ereditarietà ...

Un'altra alternativa è fornire metodi più sicuri come metodi gratuiti, poiché è sempre possibile aggiungere metodi gratuiti senza restrizioni. Potrebbe sembrare meno idiomatico per le persone con la mentalità "OO", e alcuni operatori non possono essere così aggiunti.

5

Se non si intende utilizzare il polimorfico Class (senza puntatori di base cancellazione di oggetti derivati), allora non è un comportamento indefinito.

Riferimento:

C++ 03 standard: 5.3.5 Eliminare

5.3.5/1:

L'operatore delete-espressione distrugge un oggetto più derivato (1.8) o array creato da una nuova espressione.
delete-espressione:
:: optare eliminare ghisa espressione
:: optare delete [] cast-espressione

5.3.5/3:

Nella prima alternativa (elimina oggetto), se il tipo statico dell'operando è diverso dal suo tipo dinamico, il tipo statico deve essere una classe base del tipo dinamico dell'operando e il tipo statico deve avere un distruttore virtuale o il comportamento non è definito. Nella seconda alternativa (delete array) se il tipo di dinamica dell'oggetto da eliminare differisce dal suo tipo statico, il comportamento è undefined.73)

4

Siete invitati a utilizzare quell'oggetto polimorfico, si può solo 't delete polimorficamente. Se eviti di eliminare i puntatori agli oggetti della tua classe tramite std::vector<>*, allora sei al sicuro.

parte: Si potrebbe semplificare il operator[] così:

T& operator[](unsigned n) { return this->at(n); } 
2

Sì, se non si utilizza mai il polimorfismo (vale a dire, mai upcast un riferimento o puntatore), non v'è alcun modo per eseguire una distruzione non sicuro .

Le classi di missaggio sono spesso utilizzate in questo modo e il CRTP raramente implica un distruttore virtuale, per denominare un paio di modelli.

Problemi correlati