有没有人知道在C ++ 中使用RTTI和dynamic_cast<>正确处理双重调度的方法还有一个解决方案,其中类层次结构是可扩展的,即基类可以从进一步派生出来,它的定义/实现不需要知道它?
我怀疑没有办法,但我很高兴被证明是错的:)
答案 0 :(得分:6)
C ++中的"visitor pattern"通常等同于双重调度。它不使用RTTI或dynamic_casts。
另见this question的答案。
答案 1 :(得分:6)
首先要意识到的是,双重(或更高阶)调度不会扩展。单身
调度和n
类型,您需要n
个函数;用于双重发送n^2
,依此类推。你怎么
处理此问题部分决定了您如何处理双重调度。一个明显的解决方案是
通过创建一个封闭的层次结构来限制派生类型的数量;在这种情况下,双重发送可以
使用访客模式的变体轻松实现。如果不关闭层次结构,
那么你有几种可能的方法。
如果你坚持认为每一对都对应一个函数,那么你基本上需要一个:
std::map<std::pair<std::type_index, std::type_index>, void (*)(Base const& lhs, Base const& rhs)>
dispatchMap;
(根据需要调整功能签名。)您还必须实现n^2
功能,以及
将它们插入dispatchMap
。 (我假设你使用免费功能;没有
将它们放在其中一个类而不是另一个类中的逻辑原因。)之后,您调用:
(*dispatchMap[std::make_pair( std::type_index( typeid( obj1 ) ), std::type_index( typeid( obj2 ) )])( obj1, obj2 );
(你显然想把它包装成一个函数;它不是你想分散的东西 遍布整个代码。)
一个小变体就是说只有某些组合是合法的。在这种情况下,您可以使用
find
上的dispatchMap
,如果找不到您想要的内容,则会生成错误。
(期待很多错误。)如果您可以定义某种默认值,则可以使用相同的解决方案
行为。
如果你想100%正确地完成它,一些函数能够处理中间类 和它的所有衍生物,你需要某种更动态的搜索,并订购 控制重载分辨率。考虑例如:
Base
/ \
/ \
I1 I2
/ \ / \
/ \ / \
D1a D1b D2a D2b
如果您有f(I1, D2a)
和f(D1a, I2)
,则应选择哪一个。最简单的解决方案
只是一个线性搜索,选择可以调用的第一个(由dynamic_cast
确定
指向对象的指针),并手动管理插入顺序以定义重载
你希望的决议。但是,使用n^2
函数时,这可能会很快变慢。以来
有一个排序,应该可以使用std::map
,但排序功能将会发生
实际上是非常重要的(并且仍然必须使用dynamic_cast
位)。
考虑到所有事情,我的建议是将双重调度限制在小的,封闭的层次结构中, 并坚持访客模式的某些变体。
答案 2 :(得分:3)
第一个问题是微不足道的。 dynamic_cast
涉及两件事:运行时检查和类型转换。前者需要RTTI,后者则不需要。您需要做的就是使用不需要RTTI的功能来替换dynamic_cast,就是拥有自己的方法来在运行时检查类型。要做到这一点,你需要的只是一个简单的虚函数,它返回某种类型的标识,或者它所遵循的更具体的接口(可以是枚举,整数ID,甚至是字符串)。对于强制转换,一旦您自己完成了运行时检查,就可以安全地执行static_cast
,并且您确定要转换的类型位于对象的层次结构中。因此,这解决了在不需要内置RTTI的情况下模拟dynamic_cast
的“完整”功能的问题。另一个更复杂的解决方案是创建自己的RTTI系统(就像在几个软件中完成的那样,如Matthieu提到的LLVM)。
第二个问题很重要。如何创建一个可扩展的类层次结构可以很好地扩展的双调度机制。那很难。在编译时(静态多态),这可以通过函数重载(和/或模板特化)很好地完成。在运行时,这要困难得多。据我所知,Konrad提到的唯一解决方案是保留函数指针的调度表(或者那种性质的调度表)。在我看来,通过使用静态多态和将调度函数分成类别(如函数签名和东西),可以避免违反类型安全性。但是,在实现之前,你应该非常认真地考虑你的设计,看看这个双重调度是否真的有必要,如果真的需要是一个运行时调度,并且它真的需要为每个组合都有一个单独的函数涉及两个类(也许你可以提出一个简化和固定数量的抽象类来捕获你需要实现的所有真正不同的方法。)
答案 3 :(得分:2)
您可能想要检查LLVM如何实现isa<>
,dyn_cast<>
和cast<>
作为模板系统,因为它是在没有RTTI的情况下编译的。
这有点麻烦(需要在每个类中包含花絮代码),但非常轻量级。
LLVM Programmer's Manual有一个很好的例子和对实现的引用。
(所有3种方法共享相同的代码)
答案 4 :(得分:2)
您可以通过自己实现多次调度的编译时逻辑来伪造行为。但是,这非常乏味。 Bjarne Stroustrup has co-authored a paper描述了如何在编译器中实现。
可以动态生成底层机制 - 调度表。但是,使用这种方法你当然会失去所有的语法支持。您需要维护方法指针的二维矩阵,并根据参数类型手动查找正确的方法。这将呈现一个简单的(假设的)呼叫
collision(foo, bar);
至少和
一样复杂DynamicDispatchTable::lookup(collision_signature, FooClass, BarClass)(foo, bar);
因为您不想使用RTTI。这假设您的所有方法只需要两个参数。一旦需要更多的参数(即使这些参数不是多次发送的一部分),这仍然会变得更加复杂,并且需要绕过类型安全。