d模板化转串和类继承

原文

至少在应用开发和调试阶段,toString是一种转发有关对象文本信息用户的,至关重要的方法.(向该线程道歉,我再次考虑该问题,并借用了类名):

class Animal {
    override string toString() {
        return "Animal()";
    }
}
class Dog : Animal {
    override string toString() {
        return "Dog()";
    }
}
class Bulldog : Dog {
    bool sunglasses;
    override string toString() {
        return format("Bulldog(cool: %s)", sunglasses);
    }
}

现在,即使是最顽固的GC爱好者,也应该同意,可能会出现此情况:在高强度应用循环中,可能每帧数百次调用toString,每次分配是非常不可取的.我不会费心讨论缓存值问题,因为指定的用例复杂性,在此不重要.幸好,std.format提供了非[必要]分配的替代方案:

import std.format;
import std.range.primitives;
// 强制,请见附注.
class Animal {
    void toString(W)(ref W writer) if (isOutputRange!(W, char)) {
        writer.formattedWrite("Animal()");
    }
}
class Dog : Animal {
    void toString(W)(ref W writer) if (isOutputRange!(W, char)) {
        writer.formattedWrite("Dog()");
    }
}
class Bulldog : Dog {
    bool sunglasses;
    void toString(W)(ref W writer) if (isOutputRange!(W, char)) {
        writer.formattedWrite("Bulldog(cool: %s)", sunglasses);
    }
}

但是,有个问题:

void main() {
    auto animals = [new Animal, new Dog, new Bulldog];
    foreach (animal; animals) {
        animal.writeln;
    }
}

由于该函数是模板化的,所以不可能是虚的,因此即,当仅在运行时已知类型时,要确定调用正确的toString.
当前方法有些笨拙,如涉及手动注册所有相关类,并使用通过运行时typeid``标识对象的处理器函数来代理toString,还要查找要调用的相关正确函数.很难自动确定要注册的类,尤其是,跨多个文件相互继承的类,及以后添加的类,并且增加了复杂性维护量.

考虑到toString重要性,如果编译器可用某种特例或路径来辅助它,就太好了(尽管更通用的方法也会很有趣).
我还想过,如果TypeInfo_Class可存储匹配,可在运行时调用的toString函数的类的引用,不是很好吗?,但考虑到它是模板,这是不行的.

我不知道如下是否最佳.最终会冗余检查.由于需要考虑两个级别的间接寻址(正在传递什么OutputRange模板?及正在编写对象的运行时类型?),我必须发挥一点创意.
由于无法清楚地预测toString(W)的哪些匹配项.使用isOutputRange(且可能是隐藏类型),我已直接把注册类整合到toString处理器中,但如果继承类分散到不同的文件中且不是首先调用,这仍然可能会失败.

module util.tostringmaster;
import std.string;
import std.traits;
import std.format;
import std.range.primitives;

//待办 :
//确保toString模板与期望的W版兼容
//如果不存在匹配的`toString`,则进行适当的`错误处理/回退`

abstract final class ToStringMaster {
    private:
    static abstract class ToStringBase {}
    static abstract class ToStringWriter(W) : ToStringBase {
        void writeWith(ref W writer, Object obj);
    }
    static final class ToStringHolder(W, T) : ToStringWriter!W {
        override void writeWith(ref W writer, Object obj) {
            T t = cast(T) obj;
            t.toString(writer);
        }
    }
    static ToStringBase[string][string] table;
    static void[0][string] writerTable;
    static void registerAll(W, string MOD)() {
        enum WNAME = fullyQualifiedName!W;
        enum MNAME = WNAME~":"~MOD;
        //pragma(msg, "REGISTERALL "~MNAME);
        if (MNAME in writerTable)
            return;
        writerTable.require(MNAME);
        {
            mixin(`import `~MOD~`;`);
            mixin(`alias ALL = __traits(allMembers, `~MOD~`);`);
            static foreach (sym; ALL) {{
                mixin(`alias SYM = __traits(getMember, `~MOD~`, "`~sym~`");`);
                static if (is(SYM == class)) {
                    alias CLASS = SYM;
                    alias TOSTRING = __traits(getMember, CLASS, "toString");
                    static if (__traits(isTemplate, TOSTRING)) {
                        register!(W, CLASS);
                    }
                }
            }}
            // 显式注册
            //register!(W, Animal);
            //register!(W, Dog);
            //register!(W, Bulldog);
        }
    }
    static void register(W, T)() {
        enum WNAME = fullyQualifiedName!W;
        enum TNAME = fullyQualifiedName!T;
        table.require(WNAME);
        table[WNAME][TNAME] = new ToStringHolder!(W, T);
    }
    static void redirect(W, T)(ref W writer, T obj) {
        enum WNAME = fullyQualifiedName!W;
        static if (!(WNAME.indexOf("__lambda") >= 0)) { // 不要注册hasToString等
            registerAll!(W, moduleName!(T));

            scope auto tname = typeid(obj).name;
            if (auto wp = WNAME in table) {
                if (auto p = tname in *wp) {
                    auto tsh = cast(ToStringWriter!W) *p;
                    assert(tsh, "无效ToStringWriter: "~WNAME);
                    tsh.writeWith(writer, obj);
                    return;
                }
            }
            writer.formattedWrite("<Unknown:%s>", tname);
        }
    }
}

// 插件替代项而不是闭包
/* enum string ToStringReal = q{
    static if (!(fullyQualifiedName!W.indexOf("__lambda") >= 0)) { // hasToString
        static assert(__FUNCTION__.endsWith(".toString"), "在toString中仅包含该插件");
        if (typeid(this) !is typeof(this).classinfo) {
            ToStringMaster.redirect(writer, this);
            return;
        }
    }
}; */

void realToStringOr(T,W)(T obj, ref W writer, scope void delegate() origDG = null) if (isOutputRange!(W, char)) {
    if (typeid(obj) !is T.classinfo) {
        ToStringMaster.redirect(writer, obj);
        return;
    }
    if (origDG !is null) origDG();
}
class Animal {
    void toString(W)(ref W writer) if (isOutputRange!(W, char)) {
        realToStringOr(this, writer, {
            writer.formattedWrite("Animal()");
        });
    }
}
class Dog : Animal {
    void toString(W)(ref W writer) if (isOutputRange!(W, char)) {
        realToStringOr(this, writer, {
            writer.formattedWrite("Dog()");
        });
    }
}
final class Bulldog : Dog {
    bool sunglasses;
    void toString(W)(ref W writer) if (isOutputRange!(W, char)) {
        //如果确定不会继承该类,就不需要realToStringOr,但...
        writer.formattedWrite("Bulldog(cool: %s)", sunglasses);
    }
}
void main() {
    auto animals = [new Animal, new Dog, new Bulldog];
    foreach (animal; animals) {
        animal.writeln;
    }
}
//输出:
Animal()
Dog()
Bulldog(cool: false)

附注:要使用模板化toString版本,必需导入std.range.primitives,但对该简单的要求,编译器错误是非常不直观(一大堆错误,略了).

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