【问题标题】:Get size of polymorphic object获取多态对象的大小
【发布时间】:2014-09-21 17:09:02
【问题描述】:

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

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 包含两个 最终覆盖器,分别用于来自 Base 和来自 Sized&lt;Derived&gt;size()。我们可以通过继承通过Sized来解决这个问题:

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

这按预期工作,但是在多重继承的情况下会有些混乱,并禁止更改基的可访问性/虚拟性。

那么,有没有更好的办法呢?

【问题讨论】:

  • 一个宏看起来会比你在这里做的更干净和可读...
  • 为什么需要尺寸?
  • @n.m.对于不存储已分配块大小并要求在释放时显式提供的自定义内存分配器。但这与问题无关,同样的问题代表除身份之外的任何多态类型属性(可以通过typeid 获得),例如类型名称(type_info::name() 已损坏)或通过复制构造函数定义的clone() 之类的东西。
  • 我认为一个简单的解决方案是让BaseDerive 等不直接从Sized 派生,而只是临时派生。例如。如果您在代码中的某个位置需要一个其类型派生自SizedBase 的对象,则将其包装在一个中。这是否可能/有效取决于这些类型的移动构造函数的速度。
  • @dyp,这会起作用,但会添加另一个 vtable 指针。我只会存储大小。然而,这给了我一个想法:struct SizedBase { virtual size_t size() const = 0; }; template &lt;class T&gt; struct Sized : SizedBase, T { template &lt;class... Args&gt; Sized(Args.. args) : T(args...) {} size_t size() const { return sizeof(*this); } }; template &lt;class T, class... Args&gt; T* get(Args... args) { return new (alloc.get(sizeof(Sized&lt;T&gt;))) Sized&lt;T&gt;(args...); } template &lt;class T&gt; void put(T* p) { size_t s = dynamic_cast&lt;SizedBase*&gt;(p)-&gt;size(); alloc.put(p, s); }Tfinal 时,这很遗憾地失败了。

标签: c++ polymorphism sizeof crtp


【解决方案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&lt;&gt;无处不在的时代,这不是什么大问题。

【讨论】:

  • 不错,没想到这个。但是这里的私有被破坏了:私有基地是私有的Sized&lt;&gt;,而不是Bar,因为它们无法访问。
  • 是的,“通过”模板私下继承可能是不可能的。没关系,我更新了一些不那么可怕的东西。
  • production-friendly 版本很遗憾对多线程不友好。不过添加锁很简单。
  • 当然需要加锁和适当封装东西,这只是一个想法,如果开发出来可以投入生产。
猜你喜欢
  • 1970-01-01
  • 2011-12-28
  • 2011-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-20
相关资源
最近更新 更多