You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于CRTP实现接口的C++类实例sizeof计算的疑问

CRTP实现接口时的类大小计算问题

我有一个关于使用CRTP实现接口的类实例sizeof计算的问题,示例代码如下:

#include <iostream>
class Interface
{
public:
    virtual int foo() {
        std::cout << "Interface\n";
        return 0;
        };
};

template <class Derived>
class Base : Interface
{
public:
    virtual int foo() override {
        Interface::foo();
        std::cout << "base\n";
        return static_cast<Derived*>(this)->bar2;
        }
    int bar;
};

class Derived : public Base<Derived>
{
public:
    virtual int foo() override {
            std::cout << "Derived\n";
        Base<Derived>::foo();
        return bar;
    }
    int bar2;
};

int main()
{
    std::cout << sizeof(Base<Derived>) << '\n';
    std::cout << sizeof(Derived) << '\n';
    return 0;
}

经gcc和clang编译后,sizeof(Base<Derived>)与sizeof(Derived)的输出均为16,将Base逻辑全部移至Derived类中结果也一致。我原本预期Derived实例需存储3个虚表指针与2个int成员,总大小应为40字节,因此产生以下疑问:

  1. 为何会出现该结果?
  2. 编译器是否因CRTP合并了Base<Derived>与Derived类?
  3. 如何避免额外虚表指针开销?
  4. 该实现方式是否存在隐藏开销?

问题解答

1. 为何结果是16字节?

C++中,单一继承链只会维护一个虚表指针,不管继承层级有多少层。你的类继承链是Derived -> Base<Derived> -> Interface,这是一条单一的继承路径,所以整个对象只会包含一个虚表指针(64位系统下占8字节),加上两个int成员(每个4字节,共8字节),总大小就是8+4+4=16字节,和你看到的结果一致。

你误以为会有3个虚表指针,是错误理解了虚表指针的存储规则:只有当类存在多继承(多个独立的基类都有虚函数)时,才会为每个带虚函数的基类存储一个虚表指针。单一继承链里,所有虚函数共享同一个虚表指针,子类会覆盖父类的虚表条目,不会新增虚表指针。

2. 编译器是否因CRTP合并了Base<Derived>与Derived类?

没有合并类,CRTP本身只是一种模板编程技巧,编译器不会因为CRTP就合并类的结构。这里的大小计算符合普通单一继承的规则,和CRTP无关——哪怕把Base改成非模板类,只要是单一继承,结果还是一样的。

3. 如何避免额外虚表指针开销?

如果你的场景不需要运行时多态(也就是不需要通过基类指针调用子类的虚函数),可以去掉所有virtual关键字,利用CRTP的静态多态特性来实现接口:

  • 把Interface改成纯模板的静态接口,去掉虚函数;
  • Base<Derived>中直接调用Derived的成员函数,不需要virtual;
  • Derived类不需要重写virtual函数,而是实现约定好的接口函数。

示例修改后的代码:

#include <iostream>

template <class Derived>
class Base
{
public:
    int foo() {
        std::cout << "base\n";
        return static_cast<Derived*>(this)->bar2;
    }
    int bar;
};

class Derived : public Base<Derived>
{
public:
    int foo() {
        std::cout << "Derived\n";
        Base<Derived>::foo();
        return this->bar;
    }
    int bar2;
};

int main()
{
    std::cout << sizeof(Base<Derived>) << '\n'; // 输出8(64位系统下,一个int)
    std::cout << sizeof(Derived) << '\n'; // 输出16(两个int)
}

这样修改后,类里没有虚表指针,完全通过编译期静态绑定实现接口,彻底消除虚表指针的开销。

如果必须保留运行时多态,那单一继承的结构已经是最优的,只会有一个虚表指针,无法再减少——这是运行时多态的必要开销。

4. 该实现方式是否存在隐藏开销?

在你当前的代码中:

  • 没有额外的隐藏开销,单一继承的虚表指针是运行时多态的必要成本,两个int成员是显式的存储开销;
  • CRTP本身不会带来额外开销,因为模板实例化是编译期完成的,不会产生运行时的额外成本;
  • 需要注意的是,如果你错误地使用多继承(比如让Derived同时继承多个带虚函数的基类),才会新增虚表指针的开销,你的当前代码是单一继承,不存在这个问题。

内容的提问来源于stack exchange,提问作者Ahmed AEK

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 18:46:13