使用基类指针查找派生类对象的大小

Posted

技术标签:

【中文标题】使用基类指针查找派生类对象的大小【英文标题】:find size of derived class object using base class pointer 【发布时间】:2011-10-04 02:09:23 【问题描述】:

当您不知道派生类型时,是否可以使用基类指针找到派生类对象的大小。

谢谢。

【问题讨论】:

如果您不知道类型,请使用 RTTI。 @SpeedBirdNine:不确定在这种情况下如何使用 RTTI。我是否需要知道所有派生类型 最简单的方法是在基类中添加一个 getSize() 方法,并在派生类中扩展它。 我认为 GCC 会在运行时计算 sizeof(*this) ,从而解决您的问题(创建一个虚拟函数来返回它),但我不确定。即使是这样 - 这是非标准行为。 @littleadv:按照标准 sizeof(*this) 如果在 base 上调用它应该返回 base 的大小。即使 gcc 可以在运行时做到这一点,它也不会因为它不能违反标准。 【参考方案1】:

没有直接的方法,但是你可以写一个虚拟的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-table,因为至少你可能已经有了一个虚拟析构函数。

这个特定的实现确实将您的继承树限制在一个单一的级别,而不会进入多重继承[即,从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

几个 mixin 类型的库都使用了这种方案,这样可以将 mixin 添加到现有的层次结构中,而无需引入多重继承。就我个人而言,我很少使用多于一层的继承,所以我不会为增加的复杂性而烦恼,但你的里程可能会有所不同。

【讨论】:

很好地使用CRTP。 一开始没注意,intermediate需要派生自base 意图很明确,但为了完整性,intermediate 应该继承自 base :) 哦,笨蛋。这就是我通过手机发帖的结果。 添加动态行为需要一个 vtable 和每个对象 4 个字节,对于多重继承可能更糟。 derived 的子类也会报告错误的大小。尝试通过再次继承 intermediate 来修复它会导致多重继承和多重定义 size()【参考方案2】:

我不认为可以这样做,因为sizeof 适用于编译时类型。您可以在基类中定义一个虚拟的Size 函数并为每个派生类覆盖它。

【讨论】:

如果您忘记覆盖 size 方法,子类将报告不正确的大小。【参考方案3】:

由于 C++ 中缺乏反射,这通常不可能随心所欲地使用任意类。但是,有一些解决方法。您可以按照其他人的建议编写虚拟 size() 方法。您也可以使用奇怪重复的模板模式,也就是继承自 Register&lt;T&gt;,但我不推荐它,vtable 每个对象占用 4 个字节,T 的子类报告不正确的大小,更正它会导致多重继承。

最好的方法是使用类来注册、存储和查询动态大小信息,而无需修改要查询的类:

编辑:事实证明,由于 typeid 的语义不一致,它仍然需要带有 vtables 的类,请参阅 cmets。

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

【讨论】:

使用 typeid 需要类有 vtable 才能工作(正如您已经在代码中使用虚拟 dtor 显示的那样)。因此,您的对象仍然有 vtable 开销,注册表映射的额外开销并需要类注册,因此如果您在注册之前尝试这样做,您将无法获得大小。抱歉,但这看起来并不是最好的方法。 该死,我忽略了 typeid 没有一致的语义。它确实报告了没有 vtable 的类的子类的错误类型和错误大小。你必须确保你所有的类都有虚拟析构函数,幸好有警告。而且它仍然比其他两种方法有优势,如果你忘记注册一个类,它会抛出异常,而不是报告错误的大小或需要多重继承和模棱两可的方法。 检查 size() 是否被正确覆盖并抛出异常是非常简单的,所以它不是您的解决方案的固有优势,我真的不明白为什么您需要多重继承作为解决方案使用 CRTP。 只需将 typeid(*this) 与 typeid(T) 进行比较,如果它们在 middle::size 方法中不相等则抛出异常。 很好,我不会想到这个。【参考方案4】:

考虑到@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();

【讨论】:

以上是关于使用基类指针查找派生类对象的大小的主要内容,如果未能解决你的问题,请参考以下文章

基类指针和派生类指针

基类指针和派生类指针

使用具有指向派生类对象的指针的基类指针数组调用派生类方法

基类指针指向派生类对象&派生类指针指向基类对象

派生类和基类的转换

Part7 继承与派生 7.3基类与派生类类型转换