问题
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