Получить размер полиморфного объекта

Я хочу иметь возможность получить размер полиморфного объекта. На данный момент я получил это:

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 содержит два последних переопределения для size() от Base и из Sized<Derived>, Мы можем решить это путем наследования через 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> {}; 

Это работает как задумано, однако становится несколько запутанным в случае множественного наследования и запрещает изменять доступность / виртуальность баз.

Так есть ли лучший способ?

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<> это не большая проблема.

Другие вопросы по тегам