30

我熟悉 C++ RTTI,并且觉得这个概念很有趣。

仍然存在很多滥用它的方法而不是正确使用它(RTTI-switch 恐惧浮现在脑海中)。作为一名开发人员,我发现(并使用)它只有两个可行的用途(更准确地说,一个半)。

您能否分享一些 RTTI 是解决问题的可行方法,包括示例代码/伪代码?

注意:目的是建立一个可供初级开发人员参考、批评和学习的可行示例库。

编辑:你会发现下面的代码使用 C++ RTTI

// A has a virtual destructor (i.e. is polymorphic)
// B has a virtual destructor (i.e. is polymorphic)
// B does (or does not ... pick your poison) inherits from A

void doSomething(A * a)
{
   // typeid()::name() returns the "name" of the object (not portable)
   std::cout << "a is [" << typeid(*a).name() << "]"<< std::endl ;

   // the dynamic_cast of a pointer to another will return NULL is
   // the conversion is not possible
   if(B * b = dynamic_cast<B *>(a))
   {
      std::cout << "a is b" << std::endl ;
   }
   else
   {
      std::cout << "a is NOT b" << std::endl ;
   }
}
4

10 回答 10

8

Acyclic Visitor (pdf) 很好地利用了它。

于 2008-10-26T19:40:41.987 回答
8

boost::any 对象怎么样!

这基本上使用 RTTI 信息来存储任何对象,并使用 boost::any_cast<> 检索该对象。

于 2008-10-26T21:11:07.933 回答
6

您可以将 RTTI 与 dynamic_cast 一起使用来获取指向派生类的指针,以便使用它来调用快速的类型专用算法。而不是通过基类使用虚拟方法,它将进行直接和内联调用。

这让我使用 GCC 加快了速度。Visual Studio 似乎做得不太好,它的 dynamic_cast 查找速度可能较慢。

例子:

D* obj = dynamic_cast<D*>(base);
if (obj) {
    for(unsigned i=0; i<1000; ++i)
        f(obj->D::key(i));
    }
} else {
    for(unsigned i=0; i<1000; ++i)
        f(base->key(i));
    }
}
于 2008-10-26T21:47:33.213 回答
5

我不能说我曾经在现实生活中找到过使用,但在Effective C++中提到 RTTI作为 C++ 中多方法的可能解决方案。这是因为方法分派是针对参数的动态类型而不是this参数的静态类型进行的。

class base
{
  void foo(base *b) = 0; // dynamic on the parameter type as well
};

class B : public base {...}
class B1 : public B {...}
class B2 : public B {...}

class A : public base
{
  void foo(base *b)
  {
    if (B1 *b1=dynamic_cast<B1*>(b))
      doFoo(b1);
    else if (B2 *b2=dynamic_cast<B2*>(b))
      doFoo(b2);
  }
};
于 2008-10-26T19:45:41.087 回答
3

我在序列化为 XML 文件的类树中使用它。在反序列化时,解析器类返回一个指向基类的指针,该基类具有子类类型的枚举(因为在解析它之前您不知道它是哪种类型)。如果使用该对象的代码需要引用子类特定元素,它会将枚举值和 dynamic_cast 切换到子类(由解析器创建)。这样,代码可以检查以确保解析器没有错误以及枚举值与返回的类实例类型之间不匹配。虚函数也不够用,因为您可能需要获取子类特定数据。

这只是 RTTI 可能有用的一个例子;这可能不是解决问题的最优雅的方法,但是使用 RTTI 可以使应用程序在使用这种模式时更加健壮。

于 2008-10-27T02:22:58.837 回答
3

我曾经做过一次飞机模拟,他们(有点令人困惑)称之为“模拟数据库”。您可以在其中注册浮点数、整数或字符串等变量,人们可以按名称搜索它们,并提取对它们的引用。您还可以注册一个模型(从“SimModel”派生的类的对象)。我使用 RTTI 的方式是让你可以搜索实现给定接口的模型:

SimModel* SimDatabase::FindModel<type*>(char* name="")
{
   foreach(SimModel* mo in ModelList)
   if(name == "" || mo->name eq name)
   {
       if(dynamic_cast<type*>mo != NULL)
       {
           return dynamic_cast<type*>mo;
       }
   }
   return NULL;
}

SimModel 基类:

class public SimModel
{
    public:
        void RunModel()=0;
};

一个示例接口可能是“EngineModel”:

class EngineModelInterface : public SimModel
{
    public:
        float RPM()=0;
        float FuelFlow()=0;
        void SetThrottle(float setting)=0; 
};

现在,制作莱康明和大陆发动机:

class LycomingIO540 : public EngineModelInterface 
{
    public:
        float RPM()
        {
            return rpm;
        }
        float FuelFlow()
        {
            return throttleSetting * 10.0;
        }
        void SetThrottle(float setting) 
        {
            throttleSetting = setting
        }
        void RunModel() // from SimModel base class
        {
            if(throttleSetting > 0.5)
                rpm += 1;
            else
                rpm -= 1;
        }
    private:
        float rpm, throttleSetting;
};
class Continental350: public EngineModelInterface 
{
    public:
        float RPM()
        {
            return rand();
        }
        float FuelFlow()
        {
            return rand;
        }
        void SetThrottle(float setting) 
        {
        }
        void RunModel() // from SimModel base class
        {
        }
};

现在,这里有一些有人想要引擎的代码:

.
.
EngineModelInterface * eng = simDB.FindModel<EngineModelInterface *>();
.
.
fuel = fuel - deltaTime * eng->FuelFlow();    
.
.
.

代码是相当伪的,但我希望它能传达这个想法。一个开发人员可以编写依赖于引擎的代码,但只要它有实现引擎接口的东西,它并不关心它是什么。因此,更新油箱中燃料量的代码与除了 FindModel<>() 函数和他有兴趣使用的纯虚拟 EngineModel 接口之外的所有内容完全解耦。一年后有人可以制造一个新的发动机模型,将它注册到 SimulationDatabase,上面更新燃料的人会自动开始使用它。我实际上是这样做的,因此您可以在运行时将新模型作为插件 (DLL) 加载,一旦它们在 SimulationDatabase 中注册,就可以使用 FindModel<>() 找到它们,即使正在寻找它们的代码在新 DLL 存在几个月前就已编译并构建到 DLL 中。您还可以添加从 SimModel 派生的新接口,在一个 DLL 中实现它们,在另一个 DLL 中搜索它们,一旦加载了两个 DLL,就可以执行 FindModel<>() 来获取模型另一个。即使在构建主应用程序时界面本身甚至不存在。

顺便说一句,RTTI 并不总是跨 DLL 边界工作。因为无论如何我都在使用 Qt,所以我使用qobject_castdynamic_cast. 每个类都必须从 QObject 继承(并获得 moc'd),但 qobject 元数据始终可用。如果您不关心 DLL,或者您正在使用 RTTI跨 DLL 边界工作的工具链(类型比较基于字符串比较而不是哈希或其他),那么上述所有方法都dynamic_cast可以正常工作。

于 2008-10-26T19:50:26.960 回答
2

有时static_cast,C 风格的演员表还不够,您需要dynamic_cast,例如当您拥有可怕的菱形层次结构时(图片来自 Wikipedia)。

钻石传承

struct top {
};

struct left : top { 
    int i;
    left() : i(42) {}
};

struct right : top {
    std::string name;
    right() : name("plonk") { }
};

struct bottom : left, right {
};

bottom b;
left* p = &b;

//right* r = static_cast<right*>(p); // Compilation error!
//right* r = (right*)p;              // Gives bad pointer silently 
right* r = dynamic_cast<right*>(p);  // OK
于 2008-10-27T14:51:46.250 回答
1

我在项目中的用例(如果您知道针对特定案例的更好解决方案,请发表评论):

  1. 和 1800 INFORMATION 一样的东西已经提到过:

    对于派生类,您需要一个dynamic_castfor operator==oroperator<实现。或者至少我不知道任何其他方式。

  2. 如果您想实现类似boost::any或其他一些变体容器。

  3. 在一个具有(可能的实例是和)(最多可以有一个)的Client类中的一个游戏中,我需要一个函数。这个函数很少使用,所以我想避免与一个额外的局部成员变量和处理这个的所有额外代码混淆。std::set<Player*>NetPlayerLocalPlayerLocalPlayerLocalPlayer* Client::localPlayer()Client

  4. 我有一些Variable带有几个实现的抽象类。所有已注册Variable的 s 都在 some 中std::set<Variable*> vars。并且有几个类型的内置变量BuiltinVar保存在std::vector<BuiltinVar> builtins. 在某些情况下,我有 aVariable*并且需要检查它是否是 aBuiltinVar*和 inside builtins。我可以通过一些内存范围检查或通过dynamic_cast(我可以确定在任何情况下所有实例BuiltinVar都在这个向量中)来做到这一点。

  5. 我有一个GameObjects 的网格集合,我需要检查一个网格内是否有一个Player对象(一个专门的GameObject)。我可以有一个bool GameObject::isPlayer()总是返回 false 的函数,Player或者我可以使用 RTTI。还有更多这样的例子,人们经常实现类似Object::isOfTypeXY()的功能,因此基类变得非常混乱。

    对于其他非常特殊的功能,例如Object::checkScore_doThisActionOnlyIfIAmAPlayer(). 需要一些常识来决定什么时候在基类中拥有这样的函数真正有意义,什么时候没有。

  6. 有时我将它用于断言或运行时安全检查。

  7. 有时我需要将一些数据的指针存储在某个 C 库的某个数据字段中(例如 SDL 或其他),然后我会在其他地方或作为回调得到它。我在dynamic_cast这里做一个只是为了确保我得到我所期望的。

  8. 我有一些TaskManager类执行一些Tasks 队列。对于某些Tasks,当我将它们添加到列表中时,我想Task从队列中删除其他相同类型的s。

于 2010-07-18T22:45:46.793 回答
0

几年前,我在使用 Qt 做一些基于画布的工作时使用了 RTTI。在对对象进行命中测试以使用 RTTI 来确定我将要“命中”的形状时,这非常方便。但我没有在生产代码中使用它。

于 2008-10-26T20:35:48.427 回答
0

我将它与Dynamic Double Dispatch 和 Templates一起使用。基本上,它提供了仅观察/聆听对象有趣部分的能力。

于 2008-10-27T07:47:16.217 回答