获取多态对象的大小

3

我希望能够获取多态对象的大小。目前我得到了这个:

struct Base {
    virtual std::size_t size() const {
        return sizeof(*this);
    }
};

struct Derived : Base {
    virtual std::size_t size() const {
        return sizeof(*this);
    }
};

这就是字面上的复制粘贴。我希望能做得更好。假设我真的很讨厌宏,而CRTP似乎是唯一合理的方法。让我们试试:

struct SizedBase {
    virtual std::size_t size() const = 0;
};

template <typename Type>
struct Sized : virtual SizedBase {
    std::size_t size() const override {
        return sizeof(Type);
    }
};

struct Base : Sized<Base> {}; 
struct Derived : Base, Sized<Derived> {};

这看起来好多了,但不幸的是格式有误:Derived包含两个final overriders,它们分别从BaseSized<Derived>继承了size()函数。我们可以通过Sized进行继承传递(inherit through)以解决这个问题:
struct SizedBase {
    virtual std::size_t size() const = 0;
};

template <typename Type, typename... SizedBases>
struct Sized : virtual SizedBase, SizedBases... {
    std::size_t size() const override {
        return sizeof(Type);
    }
};

struct Base : Sized<Base> {}; 
struct Derived : Sized<Derived, Base> {}; 

这种方法可以实现预期效果,但在存在多继承时会变得有些混乱,并且阻止了对基类的可访问性/虚拟性进行修改。

那么,是否有更好的方法呢?


一个宏会比你在这里所做的更加干净和易读... - egur
你为什么需要尺寸? - n. m.
@n.m. 是自定义内存分配器,它不存储分配块的大小,并要求在释放时显式提供。但这与问题无关,对于任何多态类型属性而言,同样存在相同的问题(可以通过 typeid 获取标识),例如类型名称(type_info::name() 已损坏)或类似于通过复制构造函数定义的 clone() - yuri kilochek
我认为一个简单的解决方案是让 BaseDerive 等不直接从 Sized 派生,而只是临时派生。例如,如果您在代码中的某个地方需要一个类型派生自 SizedBase 的对象,则将其包装在其中。是否可能/有效取决于这些类型的移动构造函数的速度。 - dyp
@dyp,这可能有效,但会增加另一个vtable指针。 我只需存储大小即可。 但是,这给了我一个想法:struct SizedBase { virtual size_t size() const = 0; }; template <class T> struct Sized : SizedBase, T { template <class... Args> Sized(Args.. args) : T(args...) {} size_t size() const { return sizeof(*this); } }; template <class T, class... Args> T* get(Args... args) { return new (alloc.get(sizeof(Sized<T>))) Sized<T>(args...); } template <class T> void put(T* p) { size_t s = dynamic_cast<SizedBase*>(p)->size(); alloc.put(p, s); } 当“T”为“final”时很遗憾它失败了。 - yuri kilochek
啊,是的,我曾短暂考虑过这个问题,但当我尝试其他方法时就把它忘了。确实,那个方法很好用。final 用于类是我知道的唯一一个我还不确定有用途的特性;请参见 http://akrzemi1.wordpress.com/2012/09/30/why-make-your-classes-final/ 如果类是 final 的话,你可以选择使用额外的 vptr。 - dyp
1个回答

1

虽然没有人真的应该使用这个,但是...

template <typename>
struct None1 {};
template <typename>
struct None2 {};

template <typename T>
struct PrivateBase { using Tpriv = T; using Tprot = None1<T>; using Tpub = None2<T>; };
template <typename T>
struct ProtectedBase { using Tpriv = None1<T>; using Tprot = T; using Tpub = None2<T>; };
template <typename T>
struct PublicBase { using Tpriv = None1<T>; using Tprot = None2<T>; using Tpub = T; };

template <typename K>
struct TriBase : private K::Tpriv, protected K::Tprot, public K::Tpub {};

template <typename T, typename ... Bases>
struct Sized : private Bases::Tpriv..., protected Bases::Tprot..., public Bases::Tpub...
{
    virtual size_t size() { return sizeof(T); }
};


struct Foo : Sized<Foo> {};

struct X{};
struct Y{};

struct Bar : Sized<Bar, PrivateBase<X>, ProtectedBase<Y>, PublicBase<Foo>> {};

int main ()
{
    Bar b;
    Foo* f = &b;
    X* x = &b; // error : private base
    Y* y = &b; // error : protected base
}

虚拟继承留给读者作为练习。

基类的顺序不保留,但您不应该依赖它。

可以像这样实现稍微更适合生产环境的内容(这是一个粗略的草图):

#include <cstdlib>
#include <typeinfo>
#include <unordered_map>
#include <memory>
#include <iostream>

struct myinfo
{
    size_t size;
    // any other stuff
};

using TypeInfoRef = std::reference_wrapper<const std::type_info>;
struct Hasher 
{
    std::size_t operator()(TypeInfoRef code) const
    {
        return code.get().hash_code();
    }
};

struct EqualTo 
{
    bool operator()(TypeInfoRef lhs, TypeInfoRef rhs) const
    {
        return lhs.get() == rhs.get();
    }
};

static std::unordered_map<TypeInfoRef, myinfo, Hasher, EqualTo> typemap;

template <typename K>
struct typemap_initializer
{
    typemap_initializer()
    {
        typemap[typeid(K)] = myinfo{sizeof(K)};
    }
};

struct Base
{
    virtual ~Base() {}
    size_t size() { return typemap[typeid(*this)].size; }
    template<typename K, typename... Arg>
        friend K* alloc(Arg...);
  private:
    void* operator new(size_t sz) { return ::operator new(sz); }
};

    template<typename K, typename... Arg>
K* alloc(Arg... arg)
{
    static typemap_initializer<K> ti;
    return new K(arg...);
}

struct Foo : Base {int a;};
struct Bar : Foo {int b; int c;};

int main ()
{
    Foo* f = alloc<Foo>();
    Bar* g = alloc<Bar>();

    std::cout << f->size() << std::endl;
    std::cout << g->size() << std::endl;
}

当然,这意味着我们放弃了熟悉的Foo* foo = new Foo语法,但在普及的std::make_shared<>时代,这不是一个大问题。

不错,我没有想到这一点。然而,在这里 private 是有问题的:private 基类是私有的,只对 Sized<> 可见,而在 Bar 中是无法访问的。 - yuri kilochek
是的,可能无法通过模板私有继承。不过没关系,我已经更新了更好的代码。 - n. m.
遗憾的是,生产友好版本不支持多线程。但添加锁非常简单。 - yuri kilochek
当然,一个人需要添加锁并适当封装代码,这只是一个可能被开发成产品的想法。 - n. m.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接