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

CRTP中依赖类型何时为完整类型?嵌套模板编译疑问解析

CRTP单例实现:嵌套模板写法的合法性与类型完整时机解析

问题场景

我尝试用Mixin类结合**奇异递归模板模式(CRTP)**实现单例,让类拥有一个存储自身实例的静态数据成员,且该成员知晓类的完整大小。初始写法如下:

template<class ObjectType>
class SingletonOf
{
  static inline char instance_memory[sizeof(ObjectType)]; // 编译报错:ObjectType是不完整类型
public:
  void *operator new(std::size_t)
  {
    return &instance_memory;
  }
  void operator delete(void *)
  {
  }
};

class foo : public SingletonOf<foo> // CRTP:让SingletonOf知晓foo类型
{
  // foo的数据成员与成员函数
};

void bar() {
  foo *p = new foo; // 预期调用SingletonOf<foo>::operator new返回实例内存
}

在C20中,上述代码会因ObjectType(即foo)是不完整类型而报错。但改用嵌套类模板的写法后,可通过clang的C++20模式编译:

template<class ObjectType>
class SingletonOf
{
  template<class CompleteObjectType>
  struct InstanceMemory
  {
    static char inline instance_memory[sizeof(CompleteObjectType)];
  };
public:
  void *operator new(std::size_t) {
    return InstanceMemory<ObjectType>::instance_memory;
  }
  // 其他成员实现...
};

class foo : public SingletonOf<foo>
{
  // foo的数据成员与成员函数
};

需要明确三个问题:

  1. 这种嵌套模板写法是否合法?
  2. 为何此时ObjectType会被视为完整类型?
  3. CRTP中,依赖类型名何时会成为完整类型?

1. 嵌套模板写法的合法性

该写法完全符合C++标准,是合法的实现方式。关键在于嵌套模板InstanceMemory的实例化时机被延迟,直到需要使用它的代码执行时,此时依赖的ObjectType已经是完整类型,满足sizeof操作对完整类型的要求。

2. 为何此时ObjectType被视为完整类型

初始写法的问题在于时机不对:

  • 当foo继承SingletonOf<foo>时,SingletonOf<foo>这个基类模板会立即被实例化,但此时foo的定义还没完成(还在处理基类列表和后续的成员定义),属于不完整类型,直接用sizeof(ObjectType)违反了C++标准中“不能对不完整类型使用sizeof”的规则,因此编译报错。

而嵌套模板的写法巧妙地推迟了求值时机:

  • SingletonOf<foo>实例化时,只会处理类的声明部分,成员函数operator new的函数体不会被立即实例化。
  • 只有当bar()中执行new foo时,operator new的函数体才会被实例化——此时foo的整个定义已经完成(bar函数在foo定义之后),ObjectType(即foo)已经是完整类型,InstanceMemory<foo>的实例化可以合法调用sizeof(foo)。

简单说,嵌套模板把sizeof的求值从基类模板实例化阶段推迟到了成员函数实际调用阶段,避开了派生类不完整的时间段。

3. CRTP中依赖类型名的完整时机

在CRTP场景下,派生类(即依赖类型ObjectType)的完整时机遵循以下规则:

  • 基类模板实例化时:当派生类继承CRTP基类时,派生类处于不完整状态——此时基类模板的类声明部分会被实例化,但成员函数的函数体不会被实例化(除非被显式实例化或调用)。
  • 派生类定义完成后:当派生类的所有成员、基类列表都处理完毕后,任何在派生类定义之后引用它的代码中,派生类都会被视为完整类型。
  • 成员函数实例化时:基类模板中的成员函数(比如operator new)的函数体,会在第一次被调用时实例化。如果这个调用发生在派生类定义之后,那么此时ObjectType就是完整类型。

注意:如果基类的成员函数是constexpr、consteval,或者被显式实例化,函数体的实例化会提前,此时仍可能遇到不完整类型的问题——但你的写法中operator new是普通成员函数,仅在调用时实例化,因此不会有问题。


内容的提问来源于stack exchange,提问作者cs-

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 03:45:48