如何在基类方法中获取派生类的sizeof(*this)与decltype(*this)?
首先,你的理解完全正确——如果不在每个派生类里重写GetMemoryUsage(),基类版本返回的永远是基类的大小。这是因为sizeof(*this)是编译期计算的,在基类的成员函数里,*this的静态类型就是基类,哪怕运行时实际指向的是派生类对象,也改变不了编译时确定的结果。
那怎么避免重复写return sizeof(*this)呢?这里有两个优雅的解决方案:
方案一:用CRTP(奇异递归模板模式)自动生成正确的实现
这是最省心的方式,通过模板让基类帮派生类自动搞定内存大小计算,完全不用手动重写函数。
核心思路是创建一个模板基类,让每个需要内存计算功能的类都继承它,并且把自身作为模板参数传进去。这样模板基类里的GetMemoryUsage()就能通过静态转换获取到派生类的大小:
#include <cstddef> // CRTP基类,负责提供内存计算的虚函数实现 template <typename Derived> struct MemoryTrackable { virtual ~MemoryTrackable() = default; // 必须加虚析构,不然多态删除会出问题 virtual size_t GetMemoryUsage() const { // 把*this静态转换为派生类引用,确保sizeof拿到的是派生类的大小 return sizeof(static_cast<const Derived&>(*this)); } }; // 基类A继承CRTP基类,传入自身作为模板参数 struct A : public MemoryTrackable<A> { int a; }; // 派生类B同时继承A和CRTP基类(传入B自己) struct B : public MemoryTrackable<B>, public A { int b; }; // 更深层的派生类C同理 struct C : public MemoryTrackable<C>, public B { int c; };
测试一下的话,用基类指针指向不同的派生类对象,调用GetMemoryUsage()都会返回对应对象的实际大小,完全不用在派生类里写任何重复代码。
方案二:用模板辅助函数简化重写
如果不想用CRTP,也可以在基类里写一个protected的模板辅助函数,让派生类重写时只需要一行代码调用它,避免重复写sizeof(*this):
#include <cstddef> struct A { int a; virtual ~A() = default; virtual size_t GetMemoryUsage() const = 0; protected: // 模板函数,帮派生类获取自身大小 template <typename Derived> size_t GetDerivedSize() const { return sizeof(static_cast<const Derived&>(*this)); } }; struct B : public A { int b; size_t GetMemoryUsage() const override { return GetDerivedSize<B>(); // 只需这一行 } }; struct C : public B { int c; size_t GetMemoryUsage() const override { return GetDerivedSize<C>(); } };
这种方式虽然还是需要重写函数,但代码量极小,也避免了重复的逻辑。
为什么不能只靠基类方法实现?
简单来说,sizeof是编译期确定的,基类里的sizeof(*this)从编译那一刻起就固定为基类的大小,没办法在运行时动态“感知”到派生类的实际类型。必须通过多态让派生类自己提供大小信息,而CRTP就是把这个过程自动化的最佳实践。
内容的提问来源于stack exchange,提问作者Alexey
相关产品推荐
相关产品推荐

