简体   繁体   English

避免 dynamic_cast/RTTI

[英]Avoiding dynamic_cast/RTTI

I was recently working on a piece of C++ code for a side project (the cpp-markdown library , for the curious), and ran into a coding question that I'd like some opinions on.我最近正在为一个附带项目( cpp-markdown,出于好奇)编写一段 C++ 代码,并遇到了一个我想提出一些意见的编码问题。

cpp-markdown has a base class called Token , which has a number of subclasses. cpp-markdown有一个名为Token的基础 class ,它有许多子类。 Two of the main subclasses are Container (which holds collections of other Token s) and TextHolder (used as a base class for Token s that contain text, of course).两个主要的子类是Container (其中包含其他Token的 collections )和TextHolder (当然,用作包含文本的Token的基础 class )。

Most of the processing is handled via virtual functions, but some of it was better handled in a single function.大多数处理是通过虚函数处理的,但其中一些处理最好在单个 function 中处理。 For that, I ended up using dynamic_cast to down-cast the pointer from a Token* to one of its subclasses, so I could call functions that are specific to the subclass and its child classes.为此,我最终使用dynamic_cast将指针从Token*向下转换为其子类之一,因此我可以调用特定于子类及其子类的函数。 There's no chance the casting would fail, because the code is able to tell when such a thing is needed via virtual functions (such as isUnmatchedOpenMarker ).强制转换不可能失败,因为代码能够通过虚函数(例如isUnmatchedOpenMarker )判断何时需要这样的东西。

There are two other ways I could see to handle this:我可以看到其他两种方法来处理这个问题:

  1. Create all of the functions that I want to call as virtual functions of Token , and just leave them with an empty body for every subclass except the one(s) that need to handle them, or...创建我想调用的所有函数作为Token的虚函数,并为每个子类留下一个空的主体,除了需要处理它们的子类,或者......

  2. Create a virtual function in Token that would return the properly-typed pointer to this when it's called on certain subtypes, and a null pointer if called on anything else.Token中创建一个虚拟 function ,当在某些子类型上调用this时,它将返回正确类型的指针,如果在其他任何东西上调用它,则会返回一个 null 指针。 Basically an extension of the virtual function system I'm already using there.基本上是我已经在那里使用的虚拟 function 系统的扩展。

The second method seems better than both the existing one and the first one, to me.对我来说,第二种方法似乎比现有的方法和第一种方法都好。 But I'd like to know other experienced C++ developers' views on it.但我想知道其他有经验的 C++ 开发人员对此的看法。 Or whether I'm worrying too much about trivialities.或者我是否过于担心琐事。 :-) :-)

#1 pollutes the class namespace and vtable for objects that don't need it. #1 污染了 class 命名空间和不需要它的对象的 vtable。 Ok when you have a handful of methods that will generally be implemented, but plain ugly when only needed for a single derived class.好的,当您有一些通常可以实现的方法时,但是当只需要单个派生的 class 时,就显得很丑陋了。

#2 is just dynamic_cast<> in a polka-dot dress and lipstick. #2 只是穿着圆点连衣裙和口红的dynamic_cast<> Doesn't make client code any simpler, and tangles up the entire hierarchy, requiring the base and each derived class to be semi-aware of every other derived class.不会使客户端代码更简单,并且会混淆整个层次结构,要求基础和每个派生的 class 半感知所有其他派生的 class。

Just use dynamic_cast<> .只需使用dynamic_cast<> That's what it's there for.这就是它的用途。

If you want to get clever, you could also build a double dispatch pattern, which is two-thirds of the visitor pattern .如果你想变得聪明,你还可以构建一个双分派模式,这是访问者模式的三分之二。

  • Create a base TokenVisitor class containing empty virtual visit(SpecificToken*) methods.创建一个包含空虚拟visit(SpecificToken*)方法的基础TokenVisitor class。
  • Add a single virtual accept(TokenVisitor*) method to Token that calls the properly-typed method on the passed TokenVisitor.向 Token 添加一个虚拟accept(TokenVisitor*)方法,该方法在传递的 TokenVisitor 上调用正确类型的方法。
  • Derive from TokenVisitor for the various things you will need to do in varying ways over all tokens.从 TokenVisitor 派生出您需要以不同方式对所有令牌执行的各种操作。

For the full visitor pattern, useful for tree structures, have the default accept methods iterate over children calling token->accept(this);对于对树结构有用的完整访问者模式,让默认的accept方法迭代调用token->accept(this); on each.在各个。

If you know the conversion can't be invalid then just use static_cast.如果您知道转换不会无效,那么只需使用 static_cast。

Why do you want to avoid using dynamic_cast ?为什么要避免使用dynamic_cast Is it causing an unacceptable bottle neck on your application?它是否会导致您的应用程序出现不可接受的瓶颈? If not it might not be worth doing anything to the code right now.如果不是,那么现在可能不值得对代码做任何事情。

If you are ok with trading some amount of safety for a bit of speed in your particular situation you should be fine doing a static_cast ;如果您可以在特定情况下以一定的安全性换取一点速度,那么您应该可以进行static_cast however, this is cementing your assumption that you know the type of the object and there is no chance of the cast being bad.但是,这巩固了您的假设,即您知道 object 的类型,并且演员阵容没有变坏的可能性。 If your assumption becomes wrong later, you could end up with some mysterious crash bugs in your code.如果以后您的假设变得错误,您的代码中可能会出现一些神秘的崩溃错误。 Going back to my original question, are you really sure the trade off is worth it here?回到我最初的问题,你真的确定这里的权衡值得吗?

As for the options you listed: The first doesn't really sound like a solution as much as a last minute hack I would expect to see thrown in when someone was writing code at 3 AM.至于您列出的选项:第一个听起来不像是一个解决方案,而是我希望在凌晨 3 点编写代码时看到的最后一分钟的黑客攻击。 Functionality trickling its way towards the base of the class hierarchy is one of the most common anti-patterns hit by people new to OOP.功能向 class 层次结构的基础逐渐渗透是 OOP 新手遇到的最常见的反模式之一。 Don't do it.不要这样做。

For the second option you listed any option like that are really just reimplementing dynamic_cast - if you are on a platform with only crap compilers available (I've heard stories about the Gamecube's compiler taking up a quarter of the system's available RAM with RTTI info) this might worthwhile, but more than likely you are just wasting your time.对于第二个选项,您列出的任何类似的选项实际上只是重新实现dynamic_cast - 如果您在一个只有废话编译器可用的平台上(我听说过有关 Gamecube 的编译器占用了系统可用 RAM 的四分之一以及 RTTI 信息的故事)这可能是值得的,但很可能您只是在浪费时间。 Are you really sure this is something worth concerning yourself about?你真的确定这是值得你关心的事情吗?

The true clean way to prevent dynamic_cast is to have pointers of the right type at the right place.防止 dynamic_cast 的真正干净方法是将正确类型的指针放在正确的位置。 Abstraction should take care of the rest.抽象应该注意 rest。

IMHO, the reason that dynamic_cast has this reputation is because its performance degrades a little bit every time you add another sub-type down in your class hierarchy.恕我直言,dynamic_cast 有此声誉的原因是,每次您在 class 层次结构中向下添加另一个子类型时,它的性能都会下降一点。 If you have 4-5 classes in the hierarchy, it's nothing to worry about.如果层次结构中有 4-5 个类,则无需担心。

Fun stuff.好玩的东西。 The dynamic_cast in the tokenizer implies that you want to actually split Token into something that creates the Token based on the current position in the text stream and the logic in Token.分词器中的dynamic_cast意味着您实际上希望将 Token 拆分为基于文本 stream 中的当前 position 和 Token 中的逻辑创建 Token 的东西。 Each token will either be self-contained or have to create a Token just like above to process text correctly.每个令牌要么是独立的,要么必须像上面一样创建一个令牌才能正确处理文本。

That way you pull out the generic stuff and still can parse based on the hierarchy of tokens.这样你就可以提取通用的东西,仍然可以根据令牌的层次结构进行解析。 You can even make this whole parser data-driven without using dynamic_cast .您甚至可以在不使用dynamic_cast的情况下使整个解析器由数据驱动。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM