【问题标题】:Determining size of a polymorphic C++ class确定多态 C++ 类的大小
【发布时间】:2009-09-11 15:59:53
【问题描述】:

使用sizeof 运算符,我可以确定任何类型的大小——但是如何在运行时动态确定多态类的大小?

例如,我有一个指向Animal 的指针,我想获取它所指向的实际对象的大小,如果它是Cat 或Dog,这将是不同的。有没有一种简单的方法可以做到这一点,而不是创建一个虚拟方法 Animal::size 并重载它以返回每个特定类型的 sizeof?

【问题讨论】:

  • 如果不添加虚函数,真的没有办法。为什么需要知道类的大小?

标签: c++ polymorphism sizeof


【解决方案1】:

如果您知道可能的类型集,则可以使用 RTTI 通过 dynamic_cast 找出动态类型。如果不这样做,唯一的方法是通过虚拟功能。

【讨论】:

    【解决方案2】:

    或者您可以使用 typeid,它可能比 dynamic_cast 更快(也可以使用 dynamic_cast 转换为层次结构中的中间类型)。

    看起来很糟糕:

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

    对于每一种新类型,您都需要将代码添加到 animal_size 函数中。使用虚拟大小功能,您还需要在每个类中实现此功能。然而,这个函数会简单得多(完全可以复制粘贴,这表明语言可能存在限制和代码设计问题):

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

    您可以在基类中将其抽象化,这意味着如果您忘记覆盖此方法将是编译器错误。

    编辑:这自然是假设给定任何你想知道它的大小的生物。如果您有充分的理由相信您正在处理 Dog - 或 Dog 的子类(并且您不在乎它是否是子类),那么您自然可以将 dynamic_cast 用于 ad hoc em> 测试。

    【讨论】:

    • 比不好看还糟糕,每次创建新的动物,都得修改before_size。
    • 我非常怀疑 dynamic_cast 和 typeid 不会在底层实现,基本上只是调用相同的代码(dynamic_cast 围绕它进行了一些检查,这你手动做的)。鉴于某些系统(例如涉及 DLL 时的 Windows)上的 RTTI 归结为字符串比较,如果dynamic_cast 和typeid 之间存在任何差异,它们很可能可以忽略不计.
    • @Michael:我提到过。如果您也使用 dynamic_cast,则必须修改您的代码。情况会更糟:因为 dynamic_cast 可以成功地转换为中间类型(例如从 Creature 到 Bird 的 Parrot),所以您需要更加小心如何排序这些比较!正是由于这个原因,dynamic_cast 可以实现这一点,它可能会更糟(我读过 typeid 只进行一次比较,而 dynamic_cast 实际上必须搜索继承树。)
    • @UncleBens:你确实有一个有效的观点。我没想到。
    • 动态转换和 typeid 都使用字符串比较。必须这样做,因为在大多数情况下,它可以跨越程序集边界工作。 (链接库、DLL 等)。如果您想要性能,请完全远离这些。
    【解决方案3】:

    如果您能够更改源类的设计,则可以完全用静态多态性替换动态多态性(使用虚函数)并使用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;
    }
    

    当程序所需的多态行为可以在编译时确定时(如sizeof 案例),您可以这样做,因为 CRTP 没有动态多态的灵活性来解决运行时需要的对象。

    静态多态还具有通过消除虚函数调用开销而获得更高性能的优势。

    如果您不想模板化 Base 类,或者您需要将 Base 类的不同派生实例保存在同一位置(如数组或向量),您可以在中间类上使用 CRTP 并移动多态行为到那个类(类似于维基百科中的Polymorphic copy construction example):

    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());
    

    --
    更新: 我现在在 stackoverflow 上找到了一个类似但更详细的 answer,它解释说,如果我们进一步从上面的派生类(例如 class FurtherDerived : public Derived {...})派生,则sizeof 不会正确报告。他给出了一个more complex variant 的代码来解决这个问题。

    【讨论】:

      【解决方案4】:

      我不敢相信有人发明了 type_id() 而不是实现适当的特征 ....

      【讨论】:

        【解决方案5】:

        一种稍微复杂的方法也可以通过奇怪重复的模板模式来实现这一点

        #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();
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-01-23
          • 1970-01-01
          • 1970-01-01
          • 2013-10-22
          • 2014-03-03
          • 2018-08-06
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多