开放方法从c++到d.

原地址
早前,参加c++大会,这是常式的大会,我(作者)怀疑c++,不过他们很开阔.为了学习d,我重现yomm11,觉得开放方法很不错.
成员自由函数.
可以参见c++大神最后一章多方法
开放方法虚函数,只是在类外声明.经常与多方法混淆,因为他们经常实现在一起,但他们不一样.开放更重要.
虚函数示例:

接口 动物
{
  串 踢();
}
类 狗:动物
{
  串 踢(){"吠";}
}
类 皮保罗:{
  盖 串 踢(){中 父.()~"叫";}
}
空 主()
{
  导入 标.标io:写行;
  动物 窥探=新 狗,吓唬=新 皮保罗;
  写行("窥探.踢()",窥探.());//[!注释000]
  写行("吓唬.踢()",吓唬.());//[!注释001]
}

等价开放方法,如下:

导入 开放方法;
插件(注册方法);
接口 动物{}
类 狗:动物{}
类 皮保罗:{}
串 踢(!动物);
@方法
串 _踢(狗 狗){"吠";}
@方法
串 _踢(皮保罗 狗){中 下个!()~"并叫";}
空 主()
{
  更新方法();
  导入 标.标io:写行;
  动物 窥探=新 狗,吓唬=新 皮保罗;
  写行("窥探.踢()",窥探.());//[!注释002]
  写行("吓唬.踢()",吓唬.());//[!注释003]
}

分开讲:
动物接口的变成自由函数串 踢(虚!动物);,隐式变成显式参数且带,表明运行时调用.
中的,变成自由函数,且1,@方法注解,2,函数以_开头,3,隐式本变成显式狗.
皮保罗一样,只是变成下个,主中调用,变成自由函数,只是由于统调,看起来一样.
导入开放方法后,调用注册方法插件,每个导入开放方法模块,都要这样.它匹配函数声明/重载.并创建踢函数,但不是.这是多分发入口.
主,调用更新方法.调用任何方法及每次动态加载卸载方法库时都要调用这个更新方法,一般放在主最开头.
好处是:不用修改任何类层次,就可获得多态.甚至可以添加至(对象).
假设,你编写矩阵库:有各种风格:对角,三对角,浅,稀疏,密集,可优化部分.如转置对称/对角,不用变.加稀疏矩阵不用到处加0.你用虚函数实现.很干净.
但是,我问你,你该提供永久打印功能吗?
基本上不应该,有各种矩阵,各种显示方法,应该由应用程序提供如何显示.游戏编程中,可能不需要打印函数.如果给定实现,所有代码又要加入中,不太好.
现在,应用程序,又不得不写这些打印函数.但是,他们又需要多态来满足不同矩阵的需要.导致大堆类型切换.
开放方法,则更干净.

空 打印(!矩阵 m);
@方法
空 _打印(矩阵 m)
{
  常 整 号=m.;
  常 整 nc=m.;(整 i=0;i<;++i){(整 j=0;j<nc;++j){f("%3g",m.(i,j));
    }
    写行();
  }
}
@方法
空 _打印(对角线矩阵 m)
{
  导入 标.算法;
  导入 标.格式;
  导入 标.数组;
  写行("诊断(",m.元素.映射!(x=>格式("%g",x)).合并(","),")");
}

不喜欢访问者模式:
访问者反模式,要求,基类知道所有派生类.不一定.访问者还是不错的,见访问者模式

导入 标.标io;
接口 矩阵{
  接口 访问者{
    空 访问(密集矩阵 m);
    空 访问(对角线矩阵 m);
  }
  空 接受(访问者 v);
}
类 密集矩阵:矩阵
{
  空 接受(访问者 v){v.访问();}
}
类 对角线矩阵:矩阵
{
  空 接受(访问者 v){v.访问();}
}
类 打印访问者:矩阵.访问者
{(文件 of){.of=of;}
  空 访问(密集矩阵 m){of.写行("密集");}
  空 访问(对角线矩阵 m){of.写行("对角");}
  文件 of;
}
空 主()
{
  矩阵 密集=新 密集矩阵,对角线=新 对角线矩阵;
  动 打印机=新 打印访问者(标输出);
  密集.接受(打印机);
  对角线.接受(打印机);
}

冗长,且不可扩展.如用户想添加稀疏矩阵,没办法.但用开放方法,则简单,可用,优雅:

//[!注释004]
空 打印(!矩阵 m,文件 of);
@方法
空 _打印(密集矩阵 m,文件 of)
{
  of.写行("密集");
}
@方法
空 _打印(对角线矩阵 m,文件 of)
{
  of.写行("对角");
}
//[!注释005]
类 稀疏矩阵:矩阵
{
  //[!注释006]
}
@方法
空 _打印(稀疏矩阵 m,文件 of)
{
  of.写行("稀疏");
}

多分发

根据两个或多个参数,来分发行为,许多语言只有支持单分发虚函数,只能通过类型开关/访问者来实现,一些语言通过多方法解决了,如(公共lisp),一些语言最近本地支持了:闭包/julia/Nice/Cecil/TADS.
本库也实现了.且不限制参数个数.你只需要加个虚!.
减/乘操作,各种矩阵对角/三对角/稀疏/密集等.
开放方法,没问题:

模块 矩阵;
矩阵 加(!矩阵,!矩阵);
模块 密集矩阵;
@方法
矩阵 _加(矩阵 a,矩阵 b)
{
  //通过接口加元素
  //返回密集矩阵
}
@方法
矩阵 _加(密集矩阵 a,密集矩阵 b)
{
  //直接访问
  //[!注释010]
}
模块 对角线矩阵;
@方法
矩阵 _加(对角线矩阵 a,对角线矩阵 b)
{
  //对角线加
  //返回对角矩阵
}

可扩展,插入新类型,很简单.

模块 我的矩阵;
@方法
矩阵 _加(稀疏矩阵 a,稀疏矩阵 b)
{
  //加非0元素
  //[!注释014]
}
@方法
矩阵 _加(稀疏矩阵 a,对角线矩阵 b)
{
  //仍不加所有0
  //[!注释016]
}
@方法
矩阵 _加(对角线矩阵 a,稀疏矩阵 b)
{
  中 加(b,a);//可交换
}

实现注意与性能.
用的是指针表来实现,类似普通虚函数调用.每个虚分发都有个关联方法表.作为函数声明/类/接口虚参数.默认在类信息析构器指针中存储关联类的方法表指针.虚表的第一项为类信息指针.析构器指针用来实现过时的删方法,所以重复利用它.可能会删除这个析构器指针或已利用.有替代方法.用@成针("哈希")来标记,这样更新函数时,计算完全数哈希索引来从数组中取方法表指针.等价于用整乘虚针值并应用位掩码.
方法表对每个方法,每个虚参有一个项.如方法只有虚参,则特定地址.否则该项包括:第一个参多维分发表指针,及后续参整数索引.
由于方法集,仅在运行时才知道,且动态加载时可能改变,方法表中的不是固定的.多分发时,每方法的步数可转换多维索引线性偏移.
开放方法编译器支持的虚方法差不多快.慢的原因主要有编译器,从接口还是类调用.gdc,ldc要快点.
双分发双方法好,而c++不是这样.
d的优势是模板插件,串插件,编译时反射别名.插件(注册方法)扫描整个翻译单元并:
1,检测含虚!签名来定位所有方法声明.
2,用相同签名通过串插件创建的别名,减去限定,就是用户调用的.
3,找所有@方法方法,并在运行时相应注册适当方法.
d版本更.

posted @ 2020-12-16 15:41  zjh6  阅读(17)  评论(0)    收藏  举报  来源