2009-09-11 24 views
5

Utilizando el operador sizeof, puedo determinar el tamaño de cualquier tipo, pero ¿cómo puedo determinar dinámicamente el tamaño de una clase polimórfica en tiempo de ejecución?Determinación del tamaño de una clase polimórfica C++

Por ejemplo, tengo un puntero a un Animal, y quiero obtener el tamaño del objeto real que apunta, el cual será diferente si se trata de un Cat o una Dog. ¿Hay una manera simple de hacer esto, salvo crear un método virtual Animal::size y sobrecargarlo para devolver el sizeof de cada tipo específico?

+3

Realmente no hay forma de hacerlo sin agregar una función virtual. ¿Por qué necesitas saber el tamaño de las clases? – jmucchiello

Respuesta

6

Si conoce el conjunto de tipos posibles, puede usar RTTI para averiguar el tipo dinámico al hacer dynamic_cast. Si no lo haces, la única forma es a través de una función virtual.

3

O puede usar typeid, que puede ser más rápido que dynamic_cast (también con dynamic_cast puede convertir a tipos intermedios en la jerarquía).

parece bastante malo:

#include <iostream> 
#include <typeinfo> 

class Creature 
{ 
    char x[4]; 
public: 
    virtual ~Creature() {} 
}; 

class Animal: public Creature { char x[8];}; 

class Bird: public Creature { char x[16]; }; 

class Dog: public Animal { char x[32]; }; 

class Cat: public Animal { char x[64]; }; 

class Parrot: public Bird { char x[128]; }; 

unsigned creature_size(const Creature& cr) 
{ 
    if (typeid(cr) == typeid(Animal)) { 
     return sizeof (Animal); 
    } 
    else if (typeid(cr) == typeid(Dog)) { 
     return sizeof(Dog); 
    } 
    else if (typeid(cr) == typeid(Cat)) { 
     return sizeof(Cat); 
    } 
    else if (typeid(cr) == typeid(Bird)) { 
     return sizeof(Bird); 
    } 
    else if (typeid(cr) == typeid(Parrot)) { 
     return sizeof(Parrot); 
    } 
    else if (typeid(cr) == typeid(Creature)){ 
     return sizeof(Creature); 
    } 
    assert(false && "creature_size not implemented for this type"); 
    return 0; 
} 

int main() 
{ 
    std::cout << creature_size(Creature()) << '\n' 
    << creature_size(Animal()) << '\n' 
    << creature_size(Bird()) << '\n' 
    << creature_size(Dog()) << '\n' 
    << creature_size(Cat()) << '\n' 
    << creature_size(Parrot()) << '\n' ; 
} 

Para cada nuevo tipo que necesita añadir código a la función creature_size. Con una función de tamaño virtual, necesitarás implementar esta función en cada clase también. Sin embargo, esta función será significativamente más simple (perfectamente copiar-n-pasteable, lo que demuestra que podría ser a la vez una limitación en la lengua y un problema con el diseño del código):

virtual unsigned size() const { return sizeof(*this); } 

y usted puede hacer que resumen en la clase base, lo que significa que será un error del compilador si olvida anular este método.

Editar: esto es, naturalmente, suponiendo que dada cualquier criatura que desea saber su tamaño. Si tiene una buena razón para creer que está tratando con un perro, o una subclase de perro (y no le importa si se trata de una subclase), entonces naturalmente puede usar el método dynamic_cast para una prueba ad hoc.

+2

Peor que verse mal, cada vez que creas un animal nuevo, debes modificar el tamaño de la criatura. –

+1

Dudo mucho que haya implementaciones en las que 'dynamic_cast' y' typeid' no basen, básicamente, en el mismo código (con 'dynamic_cast' envolviendo algunas comprobaciones, lo que hiciste manualmente). Dado que RTTI en algunos sistemas (por ejemplo, Windows cuando se trata de archivos DLL) se reduce a las comparaciones de cadenas, si hay alguna diferencia entre 'dynamic_cast' y' typeid' en absoluto, lo más probable es que sean negligentes. – sbi

+1

@Michael: lo mencioné. Tendrás que modificar tu código si también usaras dynamic_cast. Será aún peor: dado que dynamic_cast puede lanzar con éxito a tipos intermedios (por ejemplo, un loro de criatura a pájaro), ¡tendrás que tener más cuidado al ordenar esas comparaciones! Y precisamente por esta razón que dynamic_cast puede lograr esto, podría ser peor (he leído que typeid hace una sola comparación, mientras que dynamic_cast en realidad tiene que buscar a través del árbol de herencia.) – UncleBens

0

No puedo creer que alguien ha inventado TYPE_ID() en lugar de implementar características adecuadas ....

3

Si usted es capaz de cambiar el diseño clases Source, puede reemplazar totalmente el polimorfismo dinámico (que usa funciones virtuales) con el polimorfismo estático y utilizar el CRTP idiom:

template <class TDerived> 
class Base 
{ 
public: 
    int getSize() 
    { return sizeof(TDerived); } 

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

    int some_data; 
}; 

class Derived : public Base<Derived> 
{ 
public: 
    int some_other_data1; 
    int some_other_data2; 
}; 

class AnotherDerived : public Base<AnotherDerived> 
{ 
public: 
    int getSize() 
    { return some_unusual_calculations(); } 
    // Note that the static_cast above is required for this override to work, 
    // because we are not using virtual functions 
}; 

int main() 
{ 
    Derived d; 
    d.print(); 

    AnotherDerived ad; 
    ad.print(); 

    return 0; 
} 

Usted puede hacer esto cuando la necesaria comportamiento polimórfico del programa puede ser determinado en tiempo de compilación (como el caso sizeof), ya que el CRTP no tiene la flexibilidad de polimorfismo dinámico para resolver el objeto deseado en tiempo de ejecución.

El polimorfismo estático también tiene la ventaja de un mayor rendimiento al eliminar la sobrecarga de llamada de función virtual.

Si no desea templatar la clase Base o necesita tener diferentes instancias derivadas de la clase Base en una misma ubicación (como una matriz o un vector), puede usar CRTP en una clase media y mover la clase polimórfica comportamiento a esa clase (similar a la Polymorphic copy construction example en la Wikipedia):

class Base 
{ 
public: 
    virtual int getSize() = 0; 

    void print() 
    { 
     std::cout << getSize() << std:endl; 
    } 

    int some_data; 
}; 

template <class TDerived> 
class BaseCRTP: public Base 
{ 
public: 
    virtual int getSize() 
    { return sizeof(TDerived); } 
}; 

class Derived : public BaseCRTP<Derived> 
{ 
    // As before ... 
}; 

class AnotherDerived : public BaseCRTP<AnotherDerived> 
{ 
    // As before ... 

    // Note that although no static_cast is used in print(), 
    // the getSize() override still works due to virtual function. 
}; 

Base* obj_list1[100]; 
obj_list1[0] = new Derived(); 
obj_list1[2] = new AnotherDerived(); 

std::vector<Base*> obj_list2; 
obj_list2.push_back(new Derived()); 
obj_list2.push_back(new AnotherDerived()); 

-
actualización: ahora encontró una similar pero más detallada answer en stackOverflow que explica que si derivamos más lejos de la derivada clases anteriores (por ejemplo, class FurtherDerived : public Derived {...}), el sizeof no informa correctamente Él da un more complex variant del código para superar esto.

Cuestiones relacionadas