2009-09-11 16 views
5

Mit dem Operator sizeof kann ich die Größe eines beliebigen Typs bestimmen - aber wie kann ich die Größe einer polymorphen Klasse zur Laufzeit dynamisch bestimmen?Ermitteln der Größe einer polymorphen C++ - Klasse

Zum Beispiel habe ich einen Zeiger auf ein Animal, und ich möchte die Größe des tatsächlichen Objekt, um es auf die Punkte, die anders sein wird, wenn es ein Cat oder ein Dog ist. Gibt es eine einfache Möglichkeit, dies zu tun, kurz vor dem Erstellen einer virtuellen Methode Animal::size und überladen es die sizeof jedes bestimmten Typs zurückgeben?

+3

Es gibt wirklich nicht, es zu tun, ohne eine virtuelle Funktion hinzuzufügen. Warum musst du die Größe der Klassen wissen? – jmucchiello

Antwort

6

Wenn Sie die Menge der möglichen Typen kennen, können Sie RTTI verwenden, um den dynamischen Typ herauszufinden, indem Sie dynamic_cast tun. Wenn nicht, ist der einzige Weg durch eine virtuelle Funktion.

3

Oder Sie können typeid verwenden, das schneller als dynamic_cast sein kann (auch mit dynamic_cast können Sie auf zwischengeordnete Typen in der Hierarchie umwandeln).

Es sieht eher schlecht:

#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' ; 
} 

Für jeden neuen Typ von Code zum creature_size Funktion hinzuzufügen benötigen. Mit einer virtuellen Größenfunktion müssen Sie diese Funktion auch in jeder Klasse implementieren. Allerdings wird diese Funktion wesentlich einfacher sein (perfekt Copy-n-pasteable, die es zeigt, könnte sowohl eine Einschränkung in der Sprache und ein Problem mit dem Code-Design sein):

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

Und Sie können es abstrakt machen in die Basisklasse, was bedeutet, dass es ein Compilerfehler sein wird, wenn Sie vergessen, diese Methode zu überschreiben.

Bearbeiten: Dies ist natürlich davon ausgegangen, dass jede Kreatur wissen, wie groß sie ist. Wenn Sie einen starken Grund zu der Annahme haben, dass es sich um einen Hund oder eine Unterklasse von Hund handelt (und es ist Ihnen egal, ob es sich um eine Unterklasse handelt), können Sie natürlich dynamic_cast für einen ad hoc Test verwenden.

+2

Schlimmer als schlecht aussehen, jedes Mal, wenn Sie ein neues Tier erstellen, müssen Sie creature_size ändern. –

+1

Ich bezweifle sehr, dass es Implementierungen gibt, bei denen 'dynamic_cast' und' typeid' unter der Haube im Grunde nicht einfach den gleichen Code aufrufen (mit 'dynamic_cast' werden einige Checks darum gewickelt, was Sie manuell getan haben). Angesichts der Tatsache, dass RTTI auf einigen Systemen (z. B. Windows, wenn DLLs beteiligt sind) auf Zeichenfolgenvergleiche zurückgeht, _if_ irgendwelche Unterschiede zwischen "dynamic_cast" und "typeid" überhaupt bestehen, werden sie höchstwahrscheinlich vernachlässigbar sein. – sbi

+1

@Michael: Ich habe das erwähnt. Sie müssen Ihren Code ändern, wenn Sie auch dynamic_cast verwenden möchten. Es wird noch schlimmer: Da dynamic_cast erfolgreich auf Zwischentypen (z. B. ein Papagei von Creature nach Bird) umwandeln kann, müssen Sie sorgfältiger vorgehen, wie Sie diese Vergleiche bestellen! Und genau aus diesem Grund kann dynamic_cast dies erreichen, es könnte schlimmer sein (ich habe gelesen, dass Typid einen einzigen Vergleich durchführt, während dynamic_cast tatsächlich den Vererbungsbaum durchsuchen muss.) – UncleBens

0

Ich kann nicht glauben, dass jemand type_id erfunden hat() statt richtige Züge der Umsetzung ....

3

Wenn Sie in der Lage sind zu Quellenklassen Design zu ändern, können Sie völlig dynamischen Polymorphismus ersetzen (die virtuellen Funktionen verwendet die) mit statischem Polymorphismus und verwenden 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; 
} 

Sie können dies tun, wenn das benötigte polymorphe Verhalten von Programm kann bei der Kompilierung-Zeit (wie der sizeof Fall) bestimmt werden, da die CRTP nicht die Flexibilität hat, von dynamischem Polymorphismus um das gewünschte Objekt zur Laufzeit aufzulösen.

Der statische Polymorphismus hat auch den Vorteil einer höheren Leistung, da der Overhead für virtuelle Funktionsaufrufe entfällt.

Wenn Sie die Basisklasse nicht templatisieren möchten oder unterschiedliche abgeleitete Instanzen der Basisklasse am selben Speicherort (wie ein Array oder ein Vektor) speichern möchten, können Sie CRTP für eine Mittelklasse verwenden und die polymorphe Komponente verschieben Verhalten dieser Klasse (ähnlich dem Polymorphic copy construction example in der 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()); 

-
Update: ich fand jetzt eine ähnliche, aber detailliertere answer auf Stackoverflow was erklärt, dass, wenn wir weiter von der abgeleiteten ableiten Klassen oben (zB class FurtherDerived : public Derived {...}), wird die sizeof nicht richtig berichten. Er gibt eine more complex variant des Codes, um dies zu überwinden.

Verwandte Themen