我最近正在研究一个侧面项目的C ++代码(cpp-markdown
library,对于好奇的人),并遇到了一个我想要一些意见的编码问题。
cpp-markdown
有一个名为Token
的基类,它有许多子类。其中两个主要子类是Container
(其中包含其他Token
s和TextHolder
的集合(当然,它用作包含文本的Token
的基类)
大多数处理都是通过虚函数处理的,但有些处理在单个函数中处理得更好。为此,我最终使用dynamic_cast
将指针从Token*
向下转换为其子类之一,因此我可以调用特定于子类及其子类的函数。由于代码能够通过虚函数(例如isUnmatchedOpenMarker
)判断何时需要这样的事情,所以铸造不可能失败。
我还有两种方法可以解决这个问题:
创建我想要调用的所有函数作为Token
的虚函数,并为每个子类留下一个空体,除了一个(s)需要处理它们,或者......
在Token
中创建一个虚函数,当在某些子类型上调用时,它将返回正确类型的指针this
,如果在其他任何子类型上调用,则返回空指针。基本上是我已经在那里使用的虚拟功能系统的扩展。
对我来说,第二种方法似乎比现有方法和第一种方法都要好。但我想知道其他有经验的C ++开发人员对它的看法。或者我是否担心过多的琐事。 : - )
答案 0 :(得分:20)
#1污染类名称空间和vtable以查找不需要它的对象。好的,当你有一些通常会被实现的方法时,但只有在一个派生类需要时才显得很丑。
#2只是波尔卡圆点连衣裙和口红中的dynamic_cast<>
。不会使客户端代码变得更简单,并使整个层次结构纠缠不清,要求基类和每个派生类对每个其他派生类都不了解。
只需使用dynamic_cast<>
即可。这就是它的用途。
答案 1 :(得分:5)
如果你想变得聪明,你也可以建立double dispatch模式,这是visitor pattern的三分之二。
TokenVisitor
方法的基础visit(SpecificToken*)
类。accept(TokenVisitor*)
方法,该方法在传递的TokenVisitor上调用正确类型的方法。对于对树结构有用的完整访问者模式,使用默认的accept
方法迭代在每个上调用token->accept(this);
的子项。
答案 2 :(得分:4)
如果您知道转换无效,那么只需使用static_cast。
答案 3 :(得分:2)
为什么要避免使用dynamic_cast
?是否会在您的应用中造成不可接受的瓶颈?如果不是,现在可能不值得对代码做任何事情。
如果您可以在特定情况下以一定的速度交易一些安全性,那么您应该没事static_cast
;然而,这巩固了你的假设,你知道对象的类型,并且没有机会演员表现糟糕。如果您的假设在以后出错,您可能会在代码中遇到一些神秘的崩溃错误。回到我原来的问题,你真的相信这里的交易值得吗?
至于您列出的选项: 第一个听起来并不像解决方案那样,当有人在凌晨3点编写代码时,我希望看到它会被抛出。功能性渗透到类层次结构的基础是OOP新手遇到的最常见的反模式之一。不要这样做。
对于第二个选项,你列出的任何选项都只是重新实现dynamic_cast
- 如果你是在一个只有垃圾编译器的平台上(我听说有关Gamecube编译器的故事占用了四分之一的系统的可用内存和RTTI信息)这可能是值得的,但很可能你只是在浪费你的时间。你真的相信这是值得关注的吗?
答案 4 :(得分:2)
防止dynamic_cast的真正干净方法是在正确的位置使用正确类型的指针。抽象应该照顾其余部分。
恕我直言,dynamic_cast具有这种声誉的原因是,每次在类层次结构中添加另一个子类型时,其性能会有所下降。如果层次结构中有4-5个类,则无需担心。
答案 5 :(得分:1)
dynamic_cast
意味着您希望实际将标记拆分为基于文本流中的当前位置和标记中的逻辑创建标记的内容。每个令牌都是自包含的,或者必须像上面一样创建令牌以正确处理文本。
通过这种方式,您可以提取通用内容,并且仍然可以根据令牌的层次结构进行解析。您甚至可以在不使用dynamic_cast
的情况下对整个解析器进行数据驱动。