You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

C++类型擦除与类型论中类型擦除/类型重构的关联探究

C++类型擦除与类型论中类型擦除/类型重构的关联探究

这个问题戳中了很多C++开发者的认知盲区——明明两个概念同名,但听起来完全不是一回事,其实它们的关联比你想象的要紧密,我来一步步拆解清楚:


一、先搞懂类型论里的“类型擦除”到底是什么

你在Benjamin C. Pierce的《Types and Programming Languages》里看到的定义,是类型论中类型擦除的核心语义,我把关键原文摘出来更清楚:

大多数成熟编程语言的编译器实际上不会在运行时保留类型注解:它们仅在类型检查(以及更复杂编译器的代码生成阶段)中使用,不会出现在程序的编译结果里。实际上,程序在被求值前会被转换回无类型的形式。

[…] type annotations play no significant role at run time, in the sense that no run-time decisions are made on the basis of types: we can take a well-typed program, rewrite its type annotations in an arbitrary way, and obtain a program that behaves just the same.

很多多态语言会采用类型擦除语义:在类型检查阶段结束后,所有类型都会被擦除,得到的无类型术语会被解释或编译为机器码。

简单说,类型论里的类型擦除是编译阶段的核心语义策略:类型注解只是编译期用来校验程序合法性的工具,一旦通过校验,所有类型信息就会被彻底擦除,最终运行的代码是“无类型”的——类型完全不影响运行时的执行逻辑。甚至你随便改写合法程序的类型注解,运行结果都不会变,这就是类型擦除的终极体现:类型和运行时行为彻底解绑。

另外你提到的类型重构和它是互补关系:类型重构是从无类型代码推导出合法的类型注解,而类型擦除是把带类型的代码转成无类型的,两者就像“反向操作”。

而有意思的是:这种类型擦除其实是C++编译的默认行为!
比如你举的这个例子:

struct Foo { int i; friend bool operator==(Foo, Foo) = default; };
bool equalTo1(Foo f) { return Foo{1} == f; }
bool equalTo1(int f) { return 1 == f; }

这两个函数编译后的汇编完全一致,因为Foo的类型信息在编译后被擦除了,运行时本质就是操作内存里的整数——这完全符合类型论中类型擦除的定义:编译后类型注解消失,运行时不依赖类型做任何决策。


二、C++社区常说的“类型擦除”是另一套编程技巧

当C++开发者提到“类型擦除”时,通常指的是一种用来实现静态类型抽象的编程技巧:它能让我们把静态类型不同的对象,统一到同一个抽象接口下,静态代码无法感知具体的类型差异,但运行时能正确执行对应类型的逻辑。典型的例子就是你提到的这些:

  • 虚函数多态:比如std::unique_ptr<Foo>指向子类Bar时,静态类型是Foo&,你没法从静态代码判断实际的运行时类型,但运行时能通过虚表正确调用Bar::work()——具体的子类类型信息被“擦除”了,只暴露统一的Foo接口。
  • std::function:可以存储自由函数、成员函数、有无状态的lambda等各种可调用实体,静态类型都是std::function<Ret(Args...)>,但运行时能正确调用对应的实体,还能保留状态——不同可调用对象的类型差异被擦除了。
  • 带自定义删除器的std::shared_ptr:同一个std::shared_ptr<T>类型可以绑定不同类型的删除器,静态类型看不到删除器的差异,但运行时会调用正确的销毁逻辑。
  • std::any:可以存储任意类型的值,静态类型是std::any,但运行时能安全地取出原类型的值——具体存储的类型信息被擦除,只保留统一的“可存储任意类型”的接口。

三、两者的关联:不是重名巧合,是延伸应用

现在回到核心问题:这两个“类型擦除”到底有没有关系?答案是完全不是巧合,C++的类型擦除技术是类型论中类型擦除语义的延伸应用,可以从这两个角度理解:

1. 基础:C++的类型擦除技术依赖于编译期的类型擦除语义

C的类型擦除技术之所以能实现,本质是因为C编译本身就遵循类型论的类型擦除:所有静态类型信息在编译后都会被擦除,运行时的代码本质是无类型的。而C++的类型擦除技巧,就是在这个无类型的基础上,手动把部分类型相关的逻辑(比如调用、销毁、复制逻辑)存储起来,供运行时使用

比如std::function,编译时擦除了具体可调用对象的类型,但它内部会存储一个指向调用逻辑的指针(比如虚表或函数指针),以及对象的状态(如果有的话)——这些存储的信息,就是用来在运行时正确执行对应逻辑的。而虚函数的虚表,本质也是把编译时擦除的子类类型信息,通过虚表指针的形式在运行时保留,实现动态派发。

2. 语义上的互补:从“类型不影响运行时”到“擦除静态类型,保留运行时行为”

类型论中的类型擦除是“类型只服务于编译期校验,和运行时彻底无关”;而C++的类型擦除技术是“擦除具体的静态类型差异,只保留统一的抽象接口,同时把具体类型的必要行为逻辑存储起来,供运行时使用”。

简单说:类型论的类型擦除是“丢光所有类型信息”,而C++的类型擦除是“丢光静态类型差异,但留下运行时需要的类型行为”——它是在类型论类型擦除的基础上,为了实现静态类型系统下的动态抽象,手动“恢复”了部分类型相关的运行时逻辑。


总结

两者的同名绝对不是巧合:C的类型擦除技术,本质是基于C编译本身就有的、符合类型论定义的类型擦除语义,再通过额外的机制(虚表、函数指针+状态存储等),为静态类型不同的对象提供统一的抽象接口,同时保留运行时需要的具体行为逻辑。它是类型论中类型擦除语义在C++实际编程场景下的延伸和应用。

内容来源于stack exchange

火山引擎 最新活动