2011-10-04 19 views
14

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

спасибо.

+0

Если вы не знаете тип, используйте RTTI. – SpeedBirdNine

+0

@SpeedBirdNine: Не знаю, как я могу использовать RTTI в этом случае. Мне нужно знать все производные типы – blueskin

+1

Простейшим методом было бы добавить метод getSize() в базовый класс и расширить его в производном. – Lalaland

ответ

23

Нет прямого способа, но вы можете написать виртуальный метод 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> 
{ }; 

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

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

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

+1

Приятное использование [CRTP] (http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern). –

+0

Не заметил сначала, что «промежуточный» должен основываться на «базе». –

+0

Намерение ясно, но 'промежуточное' должно наследовать от' base' для полноты :) – sbk

3

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

+1

Подклассы сообщают о некорректном размере, если вы забудете переопределить метод размера. – Frigo

0

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

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

EDIT: Как оказалось, из-за непоследовательную семантику 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; 
} 
+0

Использование typeid требует, чтобы класс имел vtable для работы (как вы уже показали в своем коде с виртуальными dtor's). Таким образом, у вас все еще есть накладные расходы vtable для ваших объектов, дополнительные накладные расходы для карты реестра и требуется регистрация классов, поэтому вы не можете получить размер, если попытаетесь сделать это до регистрации. Извините, но на самом деле это не лучший способ. –

+0

Черт, я упустил из виду, что typeid не имеет последовательной семантики. Он действительно сообщает неправильный тип и неправильные размеры для подклассов класса без vtable. Вы должны убедиться, что у всех ваших классов есть виртуальные деструкторы, к счастью, есть предупреждения. И он по-прежнему имеет преимущества по сравнению с двумя другими методами, если вы забудете зарегистрировать класс, он будет генерировать исключение вместо сообщения о неправильных размерах или требует множественного наследования и двусмысленных методов. – Frigo

+0

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

0

Учитывая хороший ответ @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(); 
Смежные вопросы