sizeof (* this) и decltype (* this) в производных классах - PullRequest
0 голосов
/ 11 мая 2018

Предположим, есть классы:

struct A {
  int a;

  virtual size_t GetMemoryUsage() const {
    return sizeof(*this);
  }
};

struct B : public A {
  int b;
};

И может быть более глубокое наследство.

Мне нужен метод, который будет возвращать количество байтов, которое объект занимает в памяти, GetMemoryUsage() в этом случае. Обычно это может быть достигнуто с помощью sizeof(*this). Проблема (по крайней мере AFAIU) в том, что я должен переопределить метод в каждом производном классе и фактически скопировать и вставить его тело. Мне не нравится дублированный код:)

Я прав? Как я могу заставить sizeof(*this) и decltype(*this) возвращать то, что я хочу в подклассах, вызывая их только из методов базового класса? Есть ли более элегантное решение?

Ответы [ 2 ]

0 голосов
/ 11 мая 2018

Это безумно общая версия решения @ Maxim.

template<class B0, template<class...>class... Z>
struct TemplateFold {
  using type=B0;
};
template<class B0, template<class...>class... Z>
using TemplateFold_t = typename TemplateFold<B0, Z...>::type;

template<class B0, template<class...>class Z0, template<class...>class... Z>
struct TemplateFold<B0, Z0, Z...>
{
  using type=Z0< TemplateFold_t<B0, Z...> >;
};

struct ExposeTrivial {
protected:
    ~ExposeTrivial() {}
};
template<class D, class B0=ExposeTrivial, class...Bases>
struct Expose:B0, Bases... {
  // is a template because D isn't a real type when this class is instantiated:
  template<class T>
  using MakeConcreteType = TemplateFold_t< T, std::conditional_t< std::is_same<B0,ExposeTrivial>{}, T, B0 >::template Implement, Bases::template Implement... >;
  template<class...Args>
  static std::unique_ptr<D> create( Args&&... args ) {
    using ConcreteType = MakeConcreteType<D>;
    return std::unique_ptr<D>( new ConcreteType( std::forward<Args>(args)... ) );
  }
protected:
  ~Expose() {}
};

// expose one thing:
struct ExposeMemoryUsage:Expose<ExposeMemoryUsage> {
  virtual std::size_t GetMemoryUsage() const noexcept = 0;

  template<class B>
  struct Implement:B {
    using B::B;
    std::size_t GetMemoryUsage() const noexcept override final {
      return sizeof(*this);
    }
  };
protected:
  ~ExposeMemoryUsage() {}
};
// expose a different thing:
struct ExposeAlignment:Expose<ExposeAlignment>{
  virtual std::size_t GetAlignment() const noexcept = 0;
  template<class B>
  struct Implement:B {
    using B::B;
    std::size_t GetAlignment() const noexcept final override {
      return alignof(decltype(*this));
    }
  };
};

// Expose two things:
struct Bob : Expose<Bob, ExposeMemoryUsage, ExposeAlignment> {
  int x;

  Bob( int v ): x(v) {}
  virtual ~Bob() {}
};

int main() {
  std::unique_ptr<Bob> ptr = Bob::create(7);
  std::cout << ptr->x << " size:" << ptr->GetMemoryUsage() << " align:" << ptr->GetAlignment() << "\n";
 // Bob b; // does not compile
}

просто добавьте больше статических помощников типа «знает производный тип» в Exposer для увеличения функциональности.

Живой пример .


Как использовать:

Создать тип Expose. Он должен иметь чистый виртуальный член и шаблонный класс реализации, который (учитывая класс, производный от типа Expose) реализует этот чистый виртуальный член.

Он должен наследовать от Expose<OwnType> (CRTP), чтобы написать статический метод ::create для вас.

Если вы хотите наследовать от дополнительных Expose типов (т. Е. Создать два независимых интерфейса Expose, которые должны знать конкретный тип), вместо этого наследуйте от Expose< YourType, OtherExposeType, AnotherExposeType >. Не наследуйте независимо от OtherExposeType или AnotherExposeType.

Если вы сделаете это, ваш шаблон Implement не будет выбран.

Я мог бы улучшить это так, чтобы мы обнаружили Implement шаблонов как у вас, так и у ваших баз, но это больше метапрограммирование, чем я сейчас планирую.

0 голосов
/ 11 мая 2018

Вам не нужно реализовывать GetMemoryUsage для каждого из ваших производных классов вручную, просто оставьте его как чисто виртуальный. E.g.:

struct A
{
    virtual ~A() = default;
    virtual size_t GetMemoryUsage() const noexcept = 0;
};

struct B : A
{
    int b;
};

Однако при создании объектов эта функция должна быть реализована. Вы можете сделать это, используя фабричную функцию, которая «украшает» класс общей реализацией этого чисто виртуального:

// Can alternatively be defined inside function template create.
template<class T>
struct GetMemoryUsageImpl : T
{
    using T::T;
    size_t GetMemoryUsage() const noexcept final {
        return sizeof(T);
    }
};

template<class T, class... Args>
std::unique_ptr<T> create(Args&&... args) {
    return std::unique_ptr<T>(new GetMemoryUsageImpl<T>(std::forward<Args>(args)...));
}

Использование:

void f(A& a) {
    auto object_size = a.GetMemoryUsage();
}

int main() {
    auto b = create<B>();
    f(*b);
}

Вы также можете легко реализовать иерархию интерфейсов с помощью этой идиомы.

...