sizeof(*this) and decltype(*this) in derived classes
Asked Answered
T

2

3

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?

Thimbu answered 11/5, 2018 at 14:48 Comment(11)
CRTP to avoid to duplicate code (for the size). Visitor might help for derived type.Catty
I'd rather just use sizeof on the actual object than having a function for it.Zygodactyl
in general sizeof(*this) is not a good measure for the (total) memory usage. Try eg sizeof(some_string) or sizeof(some_vector)Tychonn
You missed some ; after struct definition.Pavis
Sounds like XY problem where would you use such function?Previous
I'd agree with DeiDei, the only thing that is better than no code duplication is no code. What is wrong about sizeof(B) to get the size of a B instance?Tychonn
@Slava, no this is my primary task, to calculate the size of the cache, which consists of such objects with similar inheritance. And I always have only pointer to base class.Thimbu
@Zygodactyl I don't have actual objects, only a pointer of base class type. The only way is to "ask" the object.Thimbu
Ask object of what? Why do you need to know objects size?Previous
That was a wrong duplicate question with a poor answer.Gump
@MaximEgorushkin: I don't see how duplicate what poor: title is more correct than this one, accepted answer uses CRTP instead of inheritance, (so OP really got a correct B whereas with your answer, using B would be wrong (fortunately, it is pure as A)), both answer are valid :-). decltype(*this) is not part of the duplicated question (which indeed would make it a wrong duplicate), but it is not part of your answer either.Catty
G
11

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.

Gump answered 11/5, 2018 at 15:2 Comment(7)
Nice solution. create can also be a static method of GetMemoryUsageImplPrevious
@Previous It can, if you like typing.Gump
@Thimbu Venerable ATL library is based on this idiom. I mean, this is a very old idiom.Gump
create can be a static method of A. Note that placement forms can also be useful (T* construct(void* data, Args&&...args)) if tricky to use. With a bit of work you can even make a framework that permits composing of such virtual-most-derived methods.Stagecoach
@Yakk-AdamNevraumont It can indeed. Alternatively std::make_unique<GetMemoryUsageImpl<T>>(...) but too verbose.Gump
@Yakk-AdamNevraumont Composing - yep, that is what that other linked answer demonstrates.Gump
@Catty You are right. Added function f to better demonstrate the intended usage.Gump
S
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.

Stagecoach answered 11/5, 2018 at 17:32 Comment(3)
Interesting idea. I do not think it is a good idea to merge the interface and the implementation into one class though. After all, conceptually, the sole purpose of interfaces is to enable a piece of code to work with different types of objects without knowing their implementation details as long as they expose the interface. The user of the interface only needs to see the interface definition. Only factories need the implementation. Practically, the separation of the interface and its implementations may help reduce compile times.Gump
Alternative implementations of the interface may not fit well into this design either.Gump
@maxim Firat, you need not use the implementation. But more importantly, the OP wants to implement the interfaces in a uniform way on the most derived type without having to repeat themselves. This to me is using virtual ,ethods as a kind of type erasure, not as a traditional OO contract, and in type erasure the contract often includes the full (generic, extendible) implementation.Stagecoach

© 2022 - 2024 — McMap. All rights reserved.