C ++在没有RTTI的情况下双重调度“可扩展”

| 有谁知道一种无需使用RTTI和dynamic_cast <>即可在C ++中正确处理双重调度的方法,以及一种解决方案,其中的类层次结构是可扩展的,即可以从该基础类中进一步继承该基类,并且其定义/实现不会需要知道吗? 我怀疑没有办法,但是我很高兴被证明是错误的:)     
已邀请:
        首先要意识到的是,双重(或更高阶)调度不会扩展。带单 调度和
n
类型,您需要
n
函数;用于双重分发
n^2
,依此类推。你怎么 处理此问题部分决定了您如何处理双重调度。一种明显的解决方案是 通过创建封闭的层次结构来限制派生类型的数量;在这种情况下,双重派遣可以 可以使用访问者模式的变体轻松实现。如果您不关闭层次结构, 那么您有几种可能的方法。 如果您坚持认为每一对都对应一个函数,那么您基本上需要:
std::map<std::pair<std::type_index, std::type_index>, void (*)(Base const& lhs, Base const& rhs)>
                dispatchMap;
(根据需要调整函数签名。)您还必须实现
n^2
函数,并且 将它们插入“ 5”。 (我在这里假设您使用的是自由功能;没有 将它们放在一个类而不是另一个类中的逻辑原因。)之后,您调用:
(*dispatchMap[std::make_pair( std::type_index( typeid( obj1 ) ), std::type_index( typeid( obj2 ) )])( obj1, obj2 );
(显然,您希望将其包装到一个函数中;这不是您想要分散的东西 整个代码。) 一个较小的变体是说只有某些组合才是合法的。在这种情况下,您可以使用 ѭ5上的
find
,如果找不到所需内容,则会产生错误。 (可能会出现很多错误。)如果可以定义某种默认值,则可以使用相同的解决方案 行为。 如果您想100%正确地做到这一点,并且某些功能能够处理中间类 及其所有派生,然后您需要某种更动态的搜索,并进行排序 控制过载分辨率。考虑例如:
            Base
         /       \\
        /         \\
       I1          I2
      /  \\        /  \\
     /    \\      /    \\
    D1a   D1b   D2a   D2b
如果您有
f(I1, D2a)
和an11ѭ,则应选择其中一个。最简单的解决方案 只是线性搜索,选择第一个可以调用的(由上的
dynamic_cast
确定 指向对象的指针),并手动管理插入顺序以定义重载 您想要的分辨率。但是,使用“ 2”功能时,该速度可能会很快变慢。以来 有一个命令,应该可以使用
std::map
,但是命令功能将 绝对不容易实现(并且仍然必须在整个过程中使用
dynamic_cast
地点)。 考虑到所有问题,我的建议是将双重分发限制在小型,封闭的层次结构中, 并坚持使用访客模式的某些变体。     
        C ++中的“访客模式”通常等同于双重调度。它不使用RTTI或dynamic_casts。 另请参阅此问题的答案。     
第一个问题是微不足道的。
dynamic_cast
涉及两件事:运行时检查和类型转换。前者需要RTTI,后者则不需要。用无需RTTI即可执行相同功能的功能替换dynamic_cast所需要做的就是拥有自己的方法来在运行时检查类型。为此,您需要的是一个简单的虚函数,该虚函数返回某种标识,以标识其类型或所遵循的特定接口(可以是枚举,整数ID甚至是字符串)。对于转换,您可以自己进行运行时检查,然后确定要转换的类型在对象的层次结构中,因此可以放心地执行
static_cast
。因此,这解决了无需内置RTTI即可模拟ѭ12的“完整”功能的问题。另一个涉及更多的解决方案是创建自己的RTTI系统(就像在多个软件中完成的一样,例如Matthieu提到的LLVM)。 第二个问题是一个大问题。如何创建可扩展的类层次结构很好地扩展的双重调度机制。很难。在编译时(静态多态),可以通过函数重载(和/或模板专门化)很好地完成此操作。在运行时,这要困难得多。据我所知,Konrad提到的唯一解决方案是保留函数指针的分派表(或类似性质的表)。我认为,通过使用静态多态性并将分派函数划分为类别(例如函数签名和填充),可以避免不得不违反类型安全性。但是,在实现这一点之前,您应该对设计进行认真思考,以查看是否确实需要这种双重调度,是否确实需要运行时调度,以及是否确实需要为每种组合使用单独的功能。涉及两个类(也许您可以拿出减少和固定数量的抽象类,这些抽象类捕获您需要实现的所有真正不同的方法)。     
        您可能要检查LLVM如何将
isa<>
dyn_cast<>
cast<>
实现为模板系统,因为它是在没有RTTI的情况下编译的。 这有点麻烦(在每个涉及到的类中都需要花一些代码),但是非常轻巧。 LLVM程序员手册中有一个很好的示例,并提供了对该实现的参考。 (所有3种方法共享相同的代码花絮)     
        您可以通过自己实现多个调度的编译时逻辑来伪造该行为。但是,这非常繁琐。 Bjarne Stroustrup与人合着了一篇论文,描述了如何在编译器中实现这一点。 底层机制(调度表)可以动态生成。但是,使用这种方法当然会失去所有的语法支持。您需要维护方法指针的二维矩阵,并根据参数类型手动查找正确的方法。这将导致一个简单的(假设的)调用
collision(foo, bar);
至少和
DynamicDispatchTable::lookup(collision_signature, FooClass, BarClass)(foo, bar);
因为您不想使用RTTI。这是假设您的所有方法仅采用两个参数。一旦需要更多的参数(即使这些参数不是多重调度的一部分),这将变得更加复杂,并且需要规避类型安全性。     

要回复问题请先登录注册