20

Is it possible to find the size of a derived class object using a base class pointer, when you don't know the derived type.

Thank you.

Chenna V
  • 10,185
  • 11
  • 77
  • 104
  • If you don't know the type, use RTTI. – SpeedBirdNine Oct 04 '11 at 02:11
  • @SpeedBirdNine: Not sure how I can use RTTI in this case. Do I need to know all the derived types – Chenna V Oct 04 '11 at 02:14
  • 1
    Simplest method would be to add a getSize() method to the base class, and extend it in the derived. –  Oct 04 '11 at 02:26
  • I think GCC would caclulate sizeof(*this) at run-time, thus solving your issue (create a virtual function to return it), but I'm not sure. Even if it does - it's non-standard behavior. – littleadv Oct 04 '11 at 02:29
  • @littleadv: by the standard sizeof(*this) should return the size of the base if it's called on base. Even if gcc can do this at run time, it won't because it can't violate the standard. – Daniel Oct 04 '11 at 05:22
  • @littleadv: not sure about GCC but as Dani mentioned that violates the standard – Chenna V Oct 04 '11 at 15:32
  • [Better answer](https://stackoverflow.com/a/50295157/412080). – Maxim Egorushkin May 11 '18 at 15:31

4 Answers4

27

There's no direct way, but you can write a virtual size() method child classes can implement. An intermediary templates class can automate the leg work.

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> 
{ };

This does require your hierarchy be polymorphic... however, requesting behavior based on the dynamic type of an object rather than its static type is part of the definition of polymorphic behavior. So this won't add a v-table to the average use case, since at the very least you probably already have a virtual destructor.

This particular implementation does limit your inheritance tree to a single level without getting into multiple inheritance [ie, a type derived from derived will not get its own override of size]. There is a slightly more complex variant that gets around that.

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>
{ };

Basically, this inserts an intermediate in between each actual layer of your hierarchy, each overriding size with the appropriate behavior, and deriving from the actual base type. Repeat ad nauseum.

//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

Several mixin-type libraries make use of such a scheme, such that the mixins can be added to an existing hierarchy without introducing multiple inheritance. Personally, I rarely use more than a single level of inheritance, so I don't bother with the added complexity, but your mileage may vary.

Dennis Zickefoose
  • 10,791
  • 3
  • 29
  • 38
  • 1
    Nice use of [CRTP](http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern). – Mark Ransom Oct 04 '11 at 02:46
  • Didn't notice at first, `intermediate` needs to derive from `base`. – Mark Ransom Oct 04 '11 at 03:13
  • Intent is clear, but `intermediate` should inherit from `base` for completeness :) – sbk Oct 04 '11 at 03:17
  • Oh, derp. That's what I get for posting from my phone. – Dennis Zickefoose Oct 04 '11 at 04:08
  • 1
    Addition of dynamic behaviour costs a vtable and 4 bytes per object, might be much worse for multiple inheritance. Subclasses of `derived` also report wrong size. Trying to fix it by inheriting again from `intermediate` will result in multiple inheritance and multiple definitions of `size()` – Frigo Oct 04 '11 at 05:32
  • @Frigo: It's easily fixed by adding one more TBase template parameter to intermediate, so subclasses of derived will derive from it via intermediate, and you can't avoid vtable overhead when you do need such kind of dynamic polymorphism. – Konstantin Oznobihin Oct 04 '11 at 05:44
  • @Fringo: I expanded my answer to address your concerns. The only real issue left is incorrect use... somebody can come along and provide their own override of `size` that does something crazy, or not properly inherit from the intermediate type. To that I say: documentation is your friend. Rename `base` to `base_type_dont_inherit_from_me_k_question_mark`, and put a note in the documentation for `size` that says, while virtual, it is not meant to be overridden in user code. At some point, you just have to trust your developers to know what they're doing. – Dennis Zickefoose Oct 04 '11 at 07:34
  • @Thanks Dennis. this should do the job. – Chenna V Oct 04 '11 at 15:35
3

I don't think it can be done, because sizeof works on compile time types. You could define a virtual Size function in the base class and override it for each derived class.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
0

Considering the nice answer of @Dennis Zickefoose, there's a case where you can implement multiple levels of inheritance which requires you neither to have virtual functions nor an intermediate class between each layer of inheritance and the added complexity.

And that's when all the intermediate (non-leaf) classes in the inheritance hierarchy are abstract classes, that is, they are not instantiated.

If that's the case, you can write the non-leaf abstract classes templated (again) on derived concrete types.

The example below demonstrates this:

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.
};


Sample usage:

Circle c;
c.demo();

Polyhedron p;
p.numPoints = 4;
p.demo();
Masood Khaari
  • 2,911
  • 2
  • 23
  • 40
0

Due to lack of reflection in C++, this is not generally possible with arbitrary classes at a whim. There are some workarounds however. You can write a virtual size() method as others have suggested. You can also use the Curiously Recurring Template Pattern, aka inheriting from Register<T> as well but I wouldn't recommend it, vtable costs 4 bytes per object, subclasses of T report incorrect size and correcting it results in multiple inheritance.

The best way would be to use a class to register, store and query dynamic size information, without modifying the class you want to query:

EDIT: As it turns out, due to the inconsistent semantics of typeid, it still needs classes with vtables, see the comments.

#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;
}
Frigo
  • 1,709
  • 1
  • 14
  • 32
  • Using typeid requires class to have vtable to work (as you already shown in your code with virtual dtor's). So you still have vtable overhead for your objects, additional overhead for the registry map and require classes registration, so you can't get a size if you try to do it before registration. Sorry, but it doesn't really look like the best way to go. – Konstantin Oznobihin Oct 04 '11 at 05:54
  • Damn, I overlooked that typeid does not have consistent semantics. It indeed does report wrong type and wrong sizes for subclasses of a class without vtable. You have to make sure all of your classes have virtual destructors, thankfully there are warnings fors that. And it still has advantages over the other two methods, if you forget to register a class it will throw an exception instead of reporting wrong sizes or requiring multiple inheritance and ambiguous methods. – Frigo Oct 04 '11 at 06:09
  • it's quite trivial to check that size() was correctly overriden and throw exception if not, so it's not inherent advantage of your solution and I don't really see why would you need multiple inheritance for a solution with CRTP. – Konstantin Oznobihin Oct 04 '11 at 06:19
  • just compare typeid(*this) with typeid(T) and throw exception if they are not equal in intermediate::size method. – Konstantin Oznobihin Oct 04 '11 at 07:31
  • Nice, I wouldn't have thought of this. – Frigo Oct 04 '11 at 07:51