Get the size of a polymorphic object

I want to be able to get the size of a polymorphic object. At this point, I got the following:

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

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

      

Which literally copies and pastes. I want to do better. Let's say I really hate macros and CRTP seems like the only sane approach. Let's try to try:

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

      

This looks much better, but is unfortunately ill-formed: Derived

contains two final overrides for size()

from Base

and from Sized<Derived>

. We can solve this by inheriting through 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> {}; 

      

This works as intended, but becomes somewhat confusing in the case of multiple inheritance and prohibits changing the availability / virtuality of databases.

So, is there a better way?

+3


source to share


1 answer


Not that anyone actually used this, but ...

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
}

      

Virtual inheritance is left as an exercise for the reader.

The order of the base classes is not preserved, but you shouldn't depend on it anyway.



Something that is slightly more production-friendly could be implemented like this (this is a rough sketch):

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

      

Sure, you give up the familiar syntax Foo* foo = new Foo

, but in the era of the ubiquitous, std::make_shared<>

that's not a big problem.

+1


source







All Articles