sizeof(*this) and decltype(*this) in derived classes

孤人 提交于 2019-12-22 10:45:39

问题


Suppose there are classes:

struct A {
  int a;

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

struct B : public A {
  int b;
};

And there may be deeper inheritance.

What I want is to have a method which will return the number of bytes an object occupies in memory, GetMemoryUsage() in this case. Usually it can be achieved by using sizeof(*this). The problem is (at least AFAIU) that I have to override the method in each derived class and actually copy-paste its body. I don't like duplicated code :)

Am I correct? How can I make sizeof(*this) and decltype(*this) return what I want in subclasses, by calling them only from base class's methods? Is there a more elegant solution?


回答1:


You do not have to implement GetMemoryUsage for each of your derived classes manually, just leave it as pure virtual. E.g.:

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

struct B : A
{
    int b;
};

When creating objects, however, that function must be implemented. You can do that using a factory function that "decorates" the class with a generic implementation of that pure virtual:

// 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)...));
}

Usage:

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

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

You can also implement a hierarchy of interfaces incrementally easily using this idiom.




回答2:


This is an insanely generic version of @Maxim's solution.

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
}

simply add more "knows the derived type" static helpers in Exposer to increase functionality.

Live example.


How to use:

Create a Expose type. It should have a pure virtual member, and a template Implement class that (given a class that derives from the Expose type) implements that pure virtual member.

It should inherit from Expose<OwnType> (CRTP) to write the static ::create method for you.

If you want to inherit from additional Expose types (ie, compose two independent Expose interfaces that need to know the concrete type), instead inherit from Expose< YourType, OtherExposeType, AnotherExposeType >. Don't independently inherit from OtherExposeType or AnotherExposeType.

If you do this your Implement template won't be picked up.

I could improve this so that we detect Implement templates in both you and your bases, but that is more metaprogramming than I'm up for right now.



来源:https://stackoverflow.com/questions/50294936/sizeofthis-and-decltypethis-in-derived-classes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!