d中推导属性
D中探险推导属性
推导属性是D编程
的重要组成部分.D
有大量属性,其中四类
属性与函数相关:
1,内存安全,包括,@safe,@system,和@trusted
.
2,@pure
,函数纯度,表明函数不能访问共享或全局
数据.
3,nothrow
,函数是否可抛
异常(注意这不包括错误
或其他可抛
子类),在此.
4,@nogc
,有此标记的函数
,无法从GC
分配内存.这包括编译器可能插入的隐藏分配
.
在此讲的是推导属性
,参考.因为不同
属性扩散,且D是非常生成密集
型语言(模板,CTFE
等),因此正确推导
函数属性可能会很难.D的方法是,根据正在编译
的代码推导
属性.这仅限于编译器知道必须总是有可用
源码的函数
.这包括:
1,返回auto
的函数
2,模板
函数
3,模板
内函数
4,另一个函数
内函数
5,λ函数
这里明显缺少的是普通函数
.为什么?因为可通过函数原型
,分开定义与声明
函数.
注意:即使在类模板
中,也不会推导类成员
函数.因为非模板
类成员函数是虚
的,因此必须显式
属性化他们.
那么,属性
错误时会怎样?答案是编译器会告诉你类似如下代码
中的错误
:
void foo() {
}
void main() @nogc {
foo();
}
//错误:`"@nogc"`的`"D主"`函数不能调用非`@nogc`的`"foo"`函数.
这是试调用错误
标记函数的错误消息
.很容易弄清楚并纠正,只需在foo
上加@nogc
,就可以了.
但是,错误
标记函数隐藏
在推导
函数后时会怎样?
void foo() {
}
void bar(alias f)() {
f();
}
void main() @nogc {
bar!foo();
}
//错误:`"@nogc"`的`"D主"`函数不能调用非`@nogc`的`onlineapp.bar!(foo).bar`函数.
未提及真正
问题:foo
未标记为@nogc
.只有个bar!foo
引用.现在,这也不太难弄清楚,不算最差.推导
失败时,有时问题有几个层次
.
需要正确属性
的函数,可能隐藏在10
级模板下,也可能在static foreach
内部中,因此很难弄清楚推导在干啥
.
那么如何
发现问题呢?你可一层一层挖,直到编译器搞清楚推导
失败的原因.
我选择@nogc
属性来显示工作原理
.但,其他
属性都一样.
1
技术:显式标记模板
用可推导
属性标记
模板,一般不是好主意.尤其是@trusted
属性.但此时,这是临时希望编译器挖
得更深一点.标记
模板,然后在解决
问题时去掉该标记
.我一般会在原代码行
上加个TODO
,来提醒我稍后删除
它.
如果标记
上面模板,会收到更好
的错误消息
:
void foo() {
}
void bar(alias f)() @nogc {//这里..
f();
}
void main() @nogc {
bar!foo();
}
//错误:`"@nogc"`的`"onlineapp.bar!(foo).bar'`函数不能调用非`@nogc`的`'onlineapp.foo'`函数
好!现在错误显示
了真正的问题,未标记foo
.只需要标记它,验证是否通过编译
,然后从中删除bar
的额外
属性,完成!
2
技术:复制和覆盖
第一个技术
问题是,有时会增加其余
代码的失败.如如下:
void foo() {
}
void bar(alias f)() @nogc {
f();
}
void main() @nogc {
bar!foo();
}
int x;
void allocateit() {
x = new int(42); // 实际用`GC`
}
void otherFunc() { // 无@nogc
bar!allocateit();
}
如果幸运,现在有两个
错误!在allocateit
中,它真不是@nogc
,所以标记bar
是无效的.此时,如果f
参数是@nogc
的,只想按@nogc
标记bar
.这是推导
的要点
.
要解决它,需要复制bar
,加上期望
属性,且仅在有问题
的调用
时,才用副本
.
void bar(alias f)() { // 不管
f();
}
void bar2(alias f)() @nogc { // 复制并加属性
f();
}
void main() @nogc {
bar2!foo(); // 在此得到正确的错误
}
void otherFunc() { // 无@nogc
bar!allocateit(); // 现在这成功了
}
这样,隔离了该感兴趣样例
的编译器路径
.而不用管其他情况
.在大量使用模板
的大应用中,此技术
很重要.
3
技术:用static if
这里的静如
,可根据编译时数据
,帮助做出不同策略
.如,在静态循环
中违规
调用.也许对某些参数,@nogc
模板成功,但不适合
其他参数.必须根据参数上可检测
的编译时数据
,来决定是用普通
还是特殊
属性路径.
很麻烦,而且无"正确
"方法.九分依赖触发的错误源
.有时用类型名
,有时用is
式,有时用__traits(编译)
等.无论用什么,请挑出要测试
路径,并特化
该调用.
void complicated(Args...)() {
static foreach(T; Args) {
static if(is(T == int)) bar2!T();
//特化属性路径
else bar!T(); // 普通路径
}
}
全面挖掘
如果有10
层,怎么办?一层一层太麻烦.
因为可能无法控制
所涉及的大部分代码
.其中一些甚至可能在D的标准库
中!但是不要怕(临时)
修改副本,编译成功前,不会有问题.成功后,再撤消
所有检测.
有时,我会完整复制
代码,找到问题
后再重新安装
包.不要害怕拆开
东西,只要记住哪些螺丝钉
去了哪些零件!
递归实例化推导失败
有时,如果确定模板
依赖自身,编译器会放弃
推导,并假设最坏
情况.示例:
auto forward(alias fn, Args...)(Args args) {
return fn(args);
}
T factorial(T)(T val) {
if(val == 1)
return val;
return forward!factorial(val - 1) * val;
}
void main() @nogc {
auto x = factorial(5);
}
//错误:`"@nogc"`的`D主`函数不能调用非`@nogc`的 `onlineapp.factorial!int.factorial`函数
用1
技术,可给factorial
添加@nogc
,就可编译了.
可惜,没有简单解决方法
.可显式标记factorial
为@nogc
,但这表明如果某些T
值用GC
,则不能用factorial
.有时很难诊断
,因为普通
技术不管用.
未来巨大变化!
在(2.101.0
)最新
版本的编译器中,增强了@safe
推导,因此当推导
导致编译失败
时,编译器会干大量此类
工作!以原始示例为例,并用@safe
替换@nogc
来(用2.101.0
后版本)编译.
void foo() {
}
void bar(alias f)() {
f();
}
void main() @safe {//..
bar!foo();
}
//错误:`"@safe"`的`"D主"`函数无法调用调用`"testsafe.foo"`的`"testsafe.bar!(foo).bar`的`"@system"`函数.
第二条错误消息
说,调用foo
自身,是使实例化bar
不安全的原因.不再需要bar
辅助!考虑7层深
的调用链
.让编译器解释每一层
而不必辅助,会节省
大量时间.
可惜,这仅适合@safe
代码,而不适合其他3个
属性之一.希望这些改进
,可针对所有类似属性
,从而不再辅助
代码!
希望本文章可帮你解决问题,而不用抓狂
!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现