d模板发射
原文
用不同的版本定义
编译不同的库
是灾难
.
根本原因
是编译后的目标文件
(或库
)中内容,与编译器
导入时认为的内容
不匹配.使用与导入
时不同的标志
构建模块时,可能会不同
:
库模块:
module lib;
version(with_func)
void func() {}
应用模块:
module app;
import lib;
void main() { func(); }
编译:
$ dmd -c -version=with_func lib.d
$ dmd app.d lib.o
//`app.d(5)`:错误:未定义的`func`标识
本例
中,函数
被编译到目标
文件中,但在生成
应用时,编译器
并不知道它是可用
的.
或如下编译并得到链接器
错误:
$ dmd -c lib.d
$ dmd -version=with_func app.d lib.o
//未定义`_D3lib4funcFZv`引用.
编译器
认为它在那里,但实际
上它并不在那里,这是因为库是用一组不同标志
构建的,编译器
不知道该点.
-version,-debug,-unittest,-dip1000,-checkaction,-check=contracts=x,-release
等编译标志,都可这样.
此外,由于限定符
是全局应用至模块
及导入的库模块
.要用库,还必须传递
冲突的标志!
如-unittest
,它设置了全局版本
,可能会使编译器
错误假定某些测试助手
已编译.
特别是模板发射错误
模板发射
错误是编译器不知道哪些
是预编译
的,哪些是导入
的.事实
是需要改变构建模式
.
编译器看到需要
模板,但认为已编译的目标
文件中已有了,跳过了再次输出
它.当它是正确
的,这节省了一些编译时间
.但当它是错误
的,会得到链接错误
(甚至,由于编译器
标志而不是模板实例
,使实例的ABI
与期望
的不同).
最简单方法,是在使用点总是
发射.因为模板
是不存在的,仅在实例化
时才生成代码.这也是-allinst
开关的作用(输出
所有实例),但会很慢
.
根本问题
是编译器
看到的源文件导入
和链接器
看到的目标文件
之间不匹配,方法就是去掉
该不匹配
.
最激进的是,链接器驱动的构建
略...
接近现状的D索引文件
把.di
文件从类似"头文件"
的文件
重新设想为更类似索引文件
,来紧密耦合
生成的目标
文件与源码
文件,告诉编译器
两者中的精确内容
.
工作原理:生成目标
文件(共享
或静态
库)时,生成.di
文件.同时分发
他们.di
与本次构建紧密耦合
.这样,代码
中不需要版本
块,也不应有.这样,第1例就不可能.
with_func
构建进库时,无论传递
的版本标志
,di
文件总应公开
它.
输出的函数原型
应表明,在目标
文件中有它.可用extern
来完成.不需要
函数体.
D索引
文件不用来给人阅读
,编译器
可自由地按CTFE
结果缓存
它,用字面
值替换ctfe
调用.也应提供
插件函数的原型
.示例:
// 源码
int foo() { return 55; }
enum bar = foo();
int baz = foo();
mixin("int m() { return bar; }");
编译为:
// 索引文件
extern int foo();
enum bar = 55; // 在文件中缓存`ctfe`结果
extern int baz; // 初化器的`ctfe`结果在`目标`文件中,因此把它标记为`extern`
extern int m(); // 插件在`目标`文件中,所以只输出它的`引用`
如上,针对函数
.模板呢?好吧,模板实例
只是另一个函数
.
// 源码
T foo(T)(T i) { return i; }
void useit() { int a = foo(5); }
编译为:
extern T foo(T:int)(T i) @safe pure nothrow;
// 要指定推导的属性
extern void useit();
// 或类似:
template foo(T:int) { extern T foo(T i); }
把信息
放在该索引
文件中,这样编译器
就可确切
知道,在目标
文件中,有该特定参数
的模板实例
.C++
在这方面做得相当成功.
注意,这里也列举了函数
的所有推导属性
.即,尽管无可供分析的源码
,但仍工作.对特定实例,它们总是相同
的,所以这很好用
,且需要创建正确的mangle
备份.
如果没有可用源码
,如何在新类型
上实例化模板?!这与CTFE
相同,即使已编译源码
到目标
文件中,D也需要源码
.
1,已在.di
中,2,知道在哪
找.
1
)时,闭源库,可根据uda
来找,但始终拥有,因为必须要满足CTFE
,但可能混淆(加密)
源码.
2
)时,D
索引文件同时链接到目标
和源
文件,因此在文件
中找不到内容
时,编译器
不会报错
,而是加载
模块的原源文件
,并从那里取出
.注意,因为是同一
模块的不同
方面,不能导入
原始模块.解析di
文件可能更快.
D索引
文件,也可输出类型
定义,输出所有源
至di
文件,通过懒
语义,还可加速.
对模板发射
,最重要的是:
0,从索引
文件中去掉版本
,这样,只要构建与其他全局标志
无关,始终可看到与构建版本
相同的文件.
1,在索引
文件中显式列举
目标文件中的模板实例
.
2,仍需要访问源码
来创建新实例
(在目标
相关索引
文件中按外
列举,生成实例
时,编译器也需要合并重载集
)及运行CTFE
(今天的.di
文件中,已破坏这些实例).
其他只是提高
性能.但这是不同的性能
提高.编译器不再
猜测了,它只是确定性
地推迟
静态决定了的工作.即使文件更大
,缓存ctfe
和mixin
输出可产生更快
构建.
这可同时,消除版本
不匹配,停止发射模板
,且可更快的构建
.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现