найти размер объекта производного класса, используя указатель базового класса - PullRequest
19 голосов
/ 04 октября 2011

Можно ли найти размер объекта производного класса, используя указатель базового класса, если вы не знаете производного типа.

Спасибо.

Ответы [ 4 ]

27 голосов
/ 04 октября 2011

Прямого пути нет, но вы можете написать виртуальный size() метод, который могут реализовать дочерние классы. Промежуточный класс шаблонов может автоматизировать работу ног.

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

Это требует, чтобы ваша иерархия была полиморфной ... однако, запрос поведения, основанного на динамическом типе объекта, а не на его статическом типе, является частью определения полиморфного поведения. Так что это не добавит v-таблицу к среднему сценарию использования, поскольку, по крайней мере, у вас, вероятно, уже есть виртуальный деструктор.

Эта конкретная реализация действительно ограничивает ваше дерево наследования одним уровнем, не входя в множественное наследование [т.е. тип, производный от derived, не получит свое собственное переопределение size]. Есть немного более сложный вариант, который обходит это.

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

По сути, это вставляет intermediate между каждым фактическим слоем вашей иерархии, каждый переопределяет size с соответствующим поведением и происходит из базового типа фактический . Повторите до тошноты.

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

Несколько библиотек типов миксинов используют такую ​​схему, так что миксины могут быть добавлены в существующую иерархию без использования множественного наследования. Лично я редко использую более одного уровня наследования, поэтому я не беспокоюсь о дополнительной сложности, но ваш пробег может отличаться.

3 голосов
/ 04 октября 2011

Я не думаю, что это можно сделать, потому что sizeof работает с типами времени компиляции. Вы можете определить виртуальную Size функцию в базовом классе и переопределить ее для каждого производного класса.

0 голосов
/ 09 февраля 2013

Учитывая хороший ответ @Dennis Zickefoose, есть случай, когда вы можете реализовать несколько уровней наследования, для которых не требуется ни виртуальных функций, ни промежуточного класса между каждым уровнем наследования и дополнительной сложностью.* И тогда все промежуточные (неконечные) классы в иерархии наследования являются абстрактными классами, то есть они не создаются.

Если это так, вы можете написатьне листовые абстрактные классы (опять же), созданные для производных конкретных типов.

Пример ниже демонстрирует это:

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

Пример использования:

Circle c;
c.demo();

Polyhedron p;
p.numPoints = 4;
p.demo();
0 голосов
/ 04 октября 2011

Из-за отсутствия отражения в C ++ это обычно невозможно с произвольными классами по прихоти. Однако есть несколько обходных путей. Вы можете написать виртуальный метод size (), как предлагали другие. Вы также можете использовать шаблон Curiously Recurring Template Pattern, также известный как Register<T>, но я бы не рекомендовал его, vtable стоит 4 байта на объект, подклассы T сообщают о неправильном размере, а исправление приводит к множественному наследованию.

Лучшим способом было бы использовать класс для регистрации, хранения и запроса информации о динамическом размере без изменения класса, к которому вы хотите обратиться:

РЕДАКТИРОВАТЬ : Оказывается, из-за несовместимой семантики typeid ему все еще нужны классы с vtables, см. Комментарии.

#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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...