C++ RTTI 注册表模式

C++ RTTI Registry Pattern

我有兴趣详细了解 C++ 中 RTTI 的优势和局限性。假设我有以下场景:

class C {};
class C0 : public C {};
class C1 : public C {};
...

void print(int id, C* c) {
    if (id == 0) dynamic_cast<C0 *>(c)->print();
    else if (id == 1) dynamic_cast<C0 *>(c)->print();
    ...
}

是否可以使用注册表模式实现上述示例?例如,使用这样的东西:

map<int, ?> registry;
void print(int id, C* c) {
    registry[id](c)->print();
}

只需将 print 设为虚函数即可轻松解决此问题。那么你可以简单地拥有:

void print(C* c)
{
    c->print();
}

并且它会为所有派生的 classes 做正确的事情。

但是如果你想保持 print 非虚拟,那么正如你所认识到的那样,有一个问题是像 registry[id](c)->print(); 这样的东西是如何工作的。映射的值类型是编译时事实,但您希望行为有 运行 时差。

好吧,我可以想到一种方法...通过使用虚函数。您需要创建一个 class 作为 C 的包装器,派生版本与从 C 派生的类型相匹配。一些模板的使用可能会使这个处理起来比较体面。然后映射根据指向基的指针声明,但通过派生包装器填充。

但最终这需要更多的复杂性,并且提供的好处并不比首先将 print 本身虚拟化所能实现的更多。

虽然使用多态和虚方法似乎更合适,但您可以使用类似下面的方法根据 id

进行注册和调度
class PrintCaller
{
public:
    template <typename T>
    std::size_t register_class()
    {
        m.push_back([](C* c) {
            auto* p = dynamic_cast<T*>(c);
            if (p) {
                p->print();
            } else {
                throw std::runtime_error("Incorrect type");
            }
        });
        return m.size() - 1;
    }

    void print(std::size_t id, C* c) const {
        if (id < m.size()) {
            m[id](c);
        } else {
            throw std::runtime_error("invalid id");
        }
    }

private:
    std::vector<std::function<void(C*)>> m;
};

Live example