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个属性之一.希望这些改进,可针对所有类似属性,从而不再辅助代码!

希望本文章可帮你解决问题,而不用抓狂!

posted @   zjh6  阅读(51)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示