开放方法从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版本更酷.
浙公网安备 33010602011771号