我所见过的几乎所有讨论这种事情的C++资源都告诉我,与使用RTTI(运行时类型标识)相比,我应该更喜欢多态方法。总的来说,我会认真对待这种建议,并会尝试并理解其基本原理-毕竟,C++是一头强大的野兽,很难全面理解。但是,对于这个特定问题,我正在空白处,想看看互联网可以提供什么样的建议。首先,让我总结一下到目前为止我所学到的东西,列出引起RTTI被“认为有害”的常见原因:

一些编译器不使用它/RTTI并非始终启用

我真的不赞成这种说法。这就像说我不应该使用C++ 14功能,因为那里有不支持它的编译器。但是,没有人会阻止我使用C++ 14功能。大多数项目将对他们正在使用的编译器及其配置方式产生影响。甚至引用了gcc手册页:



这说明如果我不使用RTTI,则可以将其禁用。这就像说,如果您不使用Boost,则不必链接到它。我不需要为有人使用-fno-rtti进行编译的情况进行计划。另外,在这种情况下,编译器将大声失败。

花费额外的内存/可能很慢

每当我想使用RTTI时,这意味着我需要访问类的某种类型信息或特征。如果我实现了不使用RTTI的解决方案,这通常意味着我将不得不在类中添加一些字段来存储此信息,因此memory参数实在是无效的(我会在下面进一步举例说明)。

实际上,dynamic_cast可能很慢。但是,通常有避免使用速度要求严格的情况的方法。而且我还没有看到替代方案。 This SO answer建议使用基类中定义的枚举来存储类型。这只有在您知道所有派生类都是先验的情况下才有效。这是一个很大的“如果”!

从这个答案来看,RTTI的成本似乎也不清楚。不同的人测量不同的东西。

优雅的多态设计将使RTTI不再必要

这是我认真对待的建议。在这种情况下,我根本无法提出涵盖我的RTTI用例的良好的非RTTI解决方案。让我举个例子:

假设我正在编写一个库来处理某种对象的图形。我想允许用户在使用我的库时生成自己的类型(因此enum方法不可用)。我的节点有一个基类:

class node_base
{
  public:
    node_base();
    virtual ~node_base();

    std::vector< std::shared_ptr<node_base> > get_adjacent_nodes();
};

现在,我的节点可以是不同的类型。这些怎么样:
class red_node : virtual public node_base
{
  public:
    red_node();
    virtual ~red_node();

    void get_redness();
};

class yellow_node : virtual public node_base
{
  public:
    yellow_node();
    virtual ~yellow_node();

    void set_yellowness(int);
};

hell ,为什么连这些都不是:
class orange_node : public red_node, public yellow_node
{
  public:
    orange_node();
    virtual ~orange_node();

    void poke();
    void poke_adjacent_oranges();
};

最后一个功能很有趣。这是一种编写方法:
void orange_node::poke_adjacent_oranges()
{
    auto adj_nodes = get_adjacent_nodes();
    foreach(auto node, adj_nodes) {
        // In this case, typeid() and static_cast might be faster
        std::shared_ptr<orange_node> o_node = dynamic_cast<orange_node>(node);
        if (o_node) {
             o_node->poke();
        }
    }
}

这一切看起来都很清晰。我不必在不需要它们的地方定义属性或方法,基节点类可以保持精简和有意义。没有RTTI,我应该从哪里开始?也许我可以在基类中添加一个node_type属性:
class node_base
{
  public:
    node_base();
    virtual ~node_base();

    std::vector< std::shared_ptr<node_base> > get_adjacent_nodes();

  private:
    std::string my_type;
};

std::string是类型的好主意吗?也许不是,但是我还能使用什么呢?编一个号码,希望没有人在使用它吗?另外,对于我的orange_node,如果我想使用red_node和yellow_node中的方法怎么办?每个节点是否必须存储多种类型?这看起来很复杂。

结论

这个示例似乎并不太复杂或异常(我在日常工作中使用的是类似的东西,其中节点表示通过软件控制的实际硬件,并且根据它们的不同而做不同的事情)。但是,我不知道使用模板或其他方法来执行此操作的干净方法。
请注意,我试图理解问题,而不是为我的例子辩护。我对上面链接的SO答案和this page on Wikibooks等页面的阅读似乎表明我滥用了RTTI,但我想了解原因。

因此,回到我的原始问题:为什么“纯多态性”优于使用RTTI?

最佳答案

接口(interface)描述了在给定情况下进行代码交互所需要知道的内容。一旦使用“您的整个类型层次结构”扩展了接口(interface),您的接口(interface)“表面积”就会变得很大,这使得进行推理变得更加困难。

例如,您“戳相邻的橙子”意味着我作为第三方无法模仿为橙子!您私下声明了一种橙色类型,然后使用RTTI使您的代码在与该类型进行交互时表现得特别。如果我想“变成橙色”,我必须在你的私家花园内。

现在,每个带有“橙色”的人都将与您的整个橙色类型以及与您的整个私家花园隐含地耦合,而不是具有已定义的接口(interface)。

乍一看,这看起来像是一种扩展受限接口(interface)而无需更改所有客户端的好方法(添加am_I_orange),但趋向于发生的是它使代码库僵化了,并阻止了进一步的扩展。特殊的橙色变成系统功能所固有的,并阻止您创建以不同方式实现的橙色的“橘子”替代品,并且可能消除依赖关系或优雅地解决了其他一些问题。

这确实意味着您的界面必须足以解决您的问题。从这个角度来看,为什么只需要戳橙色,如果是,为什么界面中不存在橙色?如果需要一些可以临时添加的模糊标签,可以将其添加到您的类型中:

class node_base {
  public:
    bool has_tag(tag_name);

这样可以从狭义的指定到基于广泛的标记的接口(interface)提供类似的大规模扩展。除了不是通过RTTI和实现细节(即,“如何实现?使用橙色类型?可以通过。”)来完成此操作之外,它都是通过完全不同的实现轻松模拟的。

如果需要,它甚至可以扩展到动态方法。 “你支持巴兹,汤姆和爱丽丝的争论吗?好的,请你。”从广义上讲,这比动态转换要少,因为事实是另一个对象是您所知道的类型。

现在,橘子对象可以带有橙色标记并随其实现,同时实现分离。

它仍然可能导致巨大的困惑,但是至少是消息和数据的困惑,而不是实现层次结构。

抽象是一种解耦和隐藏不确定性的游戏。它使代码更容易在本地进行推理。 RTTI在整个抽象到实现细节方面无聊。这可以使解决问题更加容易,但是却要付出代价,将您真正锁定在一个特定的实现中非常容易。

09-10 04:42
查看更多