Determining size of a polymorphic C++ class
Asked Answered
E

5

4

Using the sizeof operator, I can determine the size of any type – but how can I dynamically determine the size of a polymorphic class at runtime?

For example, I have a pointer to an Animal, and I want to get the size of the actual object it points to, which will be different if it is a Cat or a Dog. Is there a simple way to do this, short of creating a virtual method Animal::size and overloading it to return the sizeof of each specific type?

Engender answered 11/9, 2009 at 15:59 Comment(1)
There really isn't anyway to do it without adding a virtual function. Why do you need to know the size of the classes?Plummet
C
6

If you know the set of possible types, you can use RTTI to find out the dynamic type by doing dynamic_cast. If you don't, the only way is through a virtual function.

Chainman answered 11/9, 2009 at 16:26 Comment(0)
H
3

Or you can use typeid, which might be faster than dynamic_cast (also with dynamic_cast you can cast to intermediary types in the hierarchy).

It looks rather bad:

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

For each new type you'll need to add code to the creature_size function. With a virtual size function you'll need to implement this function in each class as well. However, this function will be significantly simpler (perfectly copy-n-pasteable, which shows there might be both a limitation in the language and a problem with your code design):

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

And you can make it abstract in the base class which means that it will be a compiler error if you forget to override this method.

Edit: this is naturally assuming that given any Creature you want to know its size. If you have a strong reason to believe that you are dealing with a Dog - or a subclass of Dog (and you don't care if it is a subclass), then naturally you can use dynamic_cast for an ad hoc test.

Haviland answered 11/9, 2009 at 17:47 Comment(8)
Worse than looking bad, every time you create a new animal, you have to modify creature_size.Nide
I very much doubt that there are implementations where dynamic_cast and typeid do not, under the hood, basically just call into the same code (with dynamic_cast wrapping some checks around that, which you did manually). Given that RTTI on some systems (e.g., Windows when DLLs are involved) comes down to string comparisons, if there are any differences between dynamic_cast and typeid at all, they will most likely be neglectable.Chainman
@Michael: I mentioned that. You'll have to modify your code if you were to use dynamic_cast too. It will be even worse: because dynamic_cast can successfully cast to intermediate types (e.g a Parrot from Creature to Bird), you'll need to be more careful how you order those comparisons! And precisely for this reason that dynamic_cast can achieve this, it might be worse (I've read that typeid does a single comparison, whereas dynamic_cast actually has to search through the inheritance tree.)Haviland
@UncleBens: You do have a valid point there. I hadn't thought about that.Chainman
both dynamic cast and typeid use string compares. this has to be done since in most cases for it to work across assembly boundaries. (linking in libs, DLLs etc). If you want performance, stay away from these altogether.Birdwatcher
@Chainman typeid() can very easily be implemented O(1). De-reference some pointers, fetch a value and Bob's your uncle. dynamic_cast can only be that efficient if the casted-to type is the most derived type of the object. Which will not be the case for all but one of the tests in the function discussed here.Outtalk
@PaulGroke: However, as Marius explained, due to the possibility of dynamic linking, on popular platforms, comparing std::type_info might resort to string comparisons.Chainman
@Chainman Comparing the type_info instance might use one string compare. A reinterpret_cast will often do multiple string compares (depending on the inheritance tree of the class). However it's implemented, fetching and comparing a type_info will typically be no slower (and often faster) than doing a reinterpret_cast. Of course avoiding both is the preferred solution, but if I have to use one or the other, all things being equal, I will choose type_info.Outtalk
G
3

If you are able to change source classes' design, you can totally replace dynamic polymorphism (which uses virtual functions) with static polymorphism and use the 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;
}

You can do this when the needed polymorphic behavior of program can be determined at compile-time (like the sizeof case), since the CRTP has not the flexibility of dynamic polymorphism to resolve the desired object at run-time.

The static polymorphism also has the advantage of higher performance by removing virtual-function-call overhead.

If you don't want to templatize Base class or you need to hold different derived instances of Base class in a same location (like an array or a vector), you can use CRTP on a middle class and move the polymorphic behavior to that class (similar to the Polymorphic copy construction example in the 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: I now found a similar but more detailed answer on stackoverflow which explains that if we further derive from the derived classes above (e.g. class FurtherDerived : public Derived {...}), the sizeof will not report correctly. He gives a more complex variant of the code to overcome this.

Gobioid answered 6/2, 2013 at 13:23 Comment(0)
W
0

I can't believe that somebody's invented type_id() instead of implementing proper traits ....

Weinberger answered 11/9, 2009 at 20:17 Comment(0)
G
0

One slightly convoluted way that will also work is to implement this through a Curiously Recurring Template Pattern

#include <iostream>

class Base {
public:
    virtual ~Base() {}
    virtual size_t getSize() = 0;
};

template <class T>
class BaseT : public Base {
public:
    size_t getSize() override { return sizeof(T); }
};

class Child : public BaseT<Child> {};

int main()
{
    std::unique_ptr<Base> child(new Child);
    std::cout << child->getSize();
}
Grateful answered 8/2, 2021 at 15:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.