2011-10-04 12 views
14

È possibile trovare la dimensione di un oggetto classe derivato utilizzando un puntatore classe base, quando non si conosce il tipo derivato.Trova la dimensione dell'oggetto classe derivata utilizzando il puntatore della classe base

Grazie.

+0

Se non si conosce il tipo, utilizzare RTTI. – SpeedBirdNine

+0

@SpeedBirdNine: Non sono sicuro di come sia possibile utilizzare RTTI in questo caso. Devo conoscere tutti i tipi derivati ​​ – blueskin

+1

Il metodo più semplice sarebbe quello di aggiungere un metodo getSize() alla classe base ed estenderlo nel derivato. – Lalaland

risposta

23

Non esiste un modo diretto, ma è possibile scrivere un metodo virtuale size() che le classi figlio possono implementare. Una classe di modelli intermediari può automatizzare il lavoro sulle gambe.

struct base { 
    virtual size_t size() const =0; 
    virtual ~base() { } 
}; 

template<typename T> 
struct intermediate : base { 
    virtual size_t size() const { return sizeof(T); } 
}; 

struct derived : intermediate<derived> 
{ }; 

Ciò richiede la gerarchia di essere polimorfo ... comunque, richiedere il comportamento in base al tipo dinamico di un oggetto piuttosto che il suo tipo statico è parte della definizione del comportamento polimorfico. Quindi questo non aggiungerà una v-table al caso d'uso medio, poiché per lo meno probabilmente hai già un distruttore virtuale.

Questo particolare implementazione non limitare l'albero di ereditarietà ad un unico livello senza entrare in ereditarietà multipla [vale a dire, un tipo derivato da derived non sarà possibile ottenere la propria forzatura dei size]. C'è una variante leggermente più complessa che si aggira in questo.

struct base { /*as before */ }; 

template<typename Derived, typename Base> 
struct intermediate : Base { 
    virtual size_t size() const { return sizeof(Derived); } 
}; 

struct derived : intermediate<derived, base> 
{ }; 

struct further_derived : intermediate<further_derived, derived> 
{ }; 

Fondamentalmente, si inserisce un intermediate tra ogni strato reale della gerarchia, ciascuno prevalente size con il comportamento appropriato, e derivante dal tipo di base effettivo. Ripeti fino alla nausea.

//what you want 
base >> derived 
    >> more_deriveder 
    >> most_derivedest 

//what you get 
base >> intermediate<derived, base> 
    >> derived >> intermediate<more_deriveder, derived> 
    >> more_deriveder >> intermediate<most_derivedest, more_deriveder> 
    >> most_derivedest 

diverse librerie mixin tipo fanno uso di tale sistema, in modo tale che i mixin possono essere aggiunti a una gerarchia esistente senza introdurre l'ereditarietà multipla. Personalmente, raramente uso più di un singolo livello di ereditarietà, quindi non mi preoccupo della complessità aggiunta, ma il tuo percorso può variare.

+1

Bel uso di [CRTP] (http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern). –

+0

Inizialmente non ho notato che 'intermediate' deve derivare da' base'. –

+0

L'intento è chiaro, ma 'intermedio' dovrebbe ereditare da' base' per completezza :) – sbk

3

Non penso che si possa fare, perché sizeof funziona su tipi di tempo di compilazione. È possibile definire una funzione virtuale Size nella classe base e sovrascriverla per ogni classe derivata.

+1

Le sottoclassi segnaleranno la dimensione errata se si dimentica di sovrascrivere il metodo di dimensione. – Frigo

0

A causa della mancanza di riflessione in C++, questo non è generalmente possibile con classi arbitrarie per un capriccio. Esistono tuttavia alcuni workaround. Puoi scrivere un metodo di dimensione virtuale() come altri hanno suggerito. Puoi anche utilizzare il modello di modello Curiosamente ricorrente, alias anche ereditato da Register<T> ma non lo consiglierei, i costi vtable 4 byte per oggetto, le sottoclassi di T riportano le dimensioni errate e correggendole ne risulta l'ereditarietà multipla.

Il modo migliore sarebbe quella di utilizzare una classe per registrare, archiviare e cercare informazioni dimensione dinamica, senza modificare la classe che si desidera eseguire una query:

EDIT: Come si è visto, a causa la semantica inconsistenti di typeid, ha ancora bisogno di classi con vtables, vedere i commenti.

#include <cstddef> 
#include <exception> 
#include <iostream> 
#include <map> 
#include <typeinfo> 

using namespace std; 

class ClassNotFoundException 
: public exception 
{}; 

class Register 
{ 

    public: 

     template <class T> 
     static void reg (T* = NULL) 
     { 
      // could add other qualifiers 
      v[&typeid(T)] = sizeof(T); 
      v[&typeid(const T)] = sizeof(T); 
      v[&typeid(T*)] = sizeof(T); 
      v[&typeid(const T*)] = sizeof(T); 
     } 

     template <class T> 
     static int getSize (const T& x) 
     { 
      const type_info* id = &typeid(x); 
      if(v.find(id) == v.end()){ 
       throw ClassNotFoundException(); 
      } 
      return v[id]; 
     } 

     template <class T> 
     static int getSize (T* x) 
     { 
      return getSize(*x); 
     } 

     template <class T> 
     static int getSize (const T* x) 
     { 
      return getSize(*x); 
     } 

    protected: 

     static map<const type_info*, int> v; 

}; 

map<const type_info*, int> Register::v; 

class A 
{ 
    public: 
     A() : x() {} 
     virtual ~A() {} 
    protected: 
     int x; 
}; 

class B 
: public A 
{ 
    public: 
     B() : y() {} 
     virtual ~B() {} 
    protected: 
     int y; 
}; 

int main() 
{ 
    Register::reg<A>(); 
    Register::reg<B>(); 

    A* a = new B(); 
    const A* b = new B(); 

    cout << Register::getSize(a) << endl; 
    cout << Register::getSize(b) << endl; 
} 
+0

L'uso di typeid richiede alla classe di avere vtable per funzionare (come hai già mostrato nel tuo codice con i virtual dtor). Quindi hai ancora un overhead vtable per i tuoi oggetti, un overhead aggiuntivo per la mappa del registro e richiede la registrazione delle classi, quindi non puoi ottenere una dimensione se provi a farlo prima della registrazione. Ci dispiace, ma in realtà non sembra il modo migliore per andare. –

+0

Dannazione, ho trascurato che la typeid non ha una semantica coerente. Segnala infatti errato tipo e dimensioni errate per sottoclassi di una classe senza vtable. Devi assicurarti che tutte le tue classi abbiano distruttori virtuali, fortunatamente ci sono degli avvertimenti. E ha ancora vantaggi rispetto agli altri due metodi, se ti dimentichi di registrare una classe genererà un'eccezione invece di riportare dimensioni errate o richiedere metodi ereditari multipli e ambigui. – Frigo

+0

è piuttosto banale controllare che size() sia stato sovrascritto correttamente e lanciare un'eccezione se non lo è, quindi non è un vantaggio intrinseco della soluzione e non vedo davvero perché avresti bisogno dell'ereditarietà multipla per una soluzione con CRTP. –

0

Considerando la bella risposta di @Dennis Zickefoose, c'è un caso in cui è possibile implementare più livelli di ereditarietà che richiede né avere funzioni virtuali né una classe intermedia fra ogni strato di eredità e la complessità aggiunta.

Ed è allora che tutte le intermedie (non foglia) classi nella gerarchia di ereditarietà sono astratti classi, cioè, non sono istanziati.

Se questo è il caso, è possibile scrivere le classi astratte non foglia su modelli (di nuovo) per i tipi concreti derivati.

L'esempio che segue dimostra questo: l'utilizzo

template <class TDerived> 
class Shape  // Base 
{ 
public: 
    float centerX; 
    float centerY; 

    int getSize() 
    { return sizeof(TDerived); } 

    void demo() 
    { 
     std::cout 
      << static_cast<TDerived*>(this)->getSize() 
      << std::endl; 
    } 
}; 

class Circle : public Shape<Circle> 
{ 
public: 
    float radius; 
}; 

class Square : public Shape<Square> 
{ 
    // other data... 
}; 

template <class TDerived> 
class Shape3D : public Shape<TDerived> 
    // Note that this class provides the underlying class the template argument 
    // it receives itself, and note that Shape3D is (at least conceptually) 
    // abstract because we can't directly instantiate it without providing it 
    // the concrete type we want, and because we shouldn't. 
{ 
public: 
    float centerZ; 
}; 

class Cube : public Shape3D<Cube> 
{ 
    // other data... 
}; 

class Polyhedron : public Shape3D<Polyhedron> 
{ 
public: 
    typedef float Point3D[3]; 

    int numPoints; 
    Point3D points[MAX_POINTS]; 

    int getSize() // override the polymorphic function 
    { return sizeof(numPoints) + numPoints * sizeof(Point3D); } 
    // This is for demonstration only. In real cases, care must be taken about memory alignment issues to correctly determine the size of Polyhedron. 
}; 


Esempio:

Circle c; 
c.demo(); 

Polyhedron p; 
p.numPoints = 4; 
p.demo(); 
Problemi correlati