C++:模拟 RTTI

发布于 2024-10-06 00:01:42 字数 1499 浏览 8 评论 0原文

我有一个这样的类层次结构:

class A        { }                            //
class AA  : A  { }                            //          A
class AAA : AA { }                            //        /   \
class AAB : AA { }                            //      AA     AB
class AB  : A  { }                            //     / \     / \
class ABA : AB { }                            //   AAA AAB ABA ABB
class ABB : AB { }                            //

我想为这个层次结构模拟 RTTI(当然不使用它),在某种程度上,给定一个指向 A 的指针/引用,我可以找出它的实际类型(类似于 typeid 的作用),作为标识类的整数。

此外,我希望标识我的类型的整数集是连续的,并且从 0 到 N-1(在我的示例中从 0 到 6):(

class A        { virtual int t(){return 0;} } //
class AA  : A  { virtual int t(){return 1;} } //            A(0)
class AAA : AA { virtual int t(){return 2;} } //          /      \
class AAB : AA { virtual int t(){return 3;} } //      AA(1)       AB(4)
class AB  : A  { virtual int t(){return 4;} } //     /   \        /    \
class ABA : AB { virtual int t(){return 5;} } // AAA(2) AAB(3) ABA(5) ABB(6)
class ABB : AB { virtual int t(){return 6;} } //

顺序并不重要:A::t<例如, /code> 可以返回 3 和 AAB::t 0 ,


是否可以让编译器将索引分配给我的类

? .wikipedia.org/wiki/Curiously_recurring_template_pattern" rel="noreferrer">CRTP 可以帮助我;类似:

class X : A, AssignFirstAvailableIndex< X > { }

但我对模板不够擅长。我如何实现 AssignFirstAvailableIndex模板类?

(当然编译器可以在编译时看到所有的类)

I've got a class hierarchy as this one:

class A        { }                            //
class AA  : A  { }                            //          A
class AAA : AA { }                            //        /   \
class AAB : AA { }                            //      AA     AB
class AB  : A  { }                            //     / \     / \
class ABA : AB { }                            //   AAA AAB ABA ABB
class ABB : AB { }                            //

I'd like to emulate RTTI (without using it, of course) for this ierarchy, in a way that, given a pointer/reference to A, I can find out its actual type (similarly to what typeid does), as an integer identifying the class.

Moreover I'd like that the set of integers identifying my types is contiguous and goes from 0 to N-1 (from 0 to 6 in my example):

class A        { virtual int t(){return 0;} } //
class AA  : A  { virtual int t(){return 1;} } //            A(0)
class AAA : AA { virtual int t(){return 2;} } //          /      \
class AAB : AA { virtual int t(){return 3;} } //      AA(1)       AB(4)
class AB  : A  { virtual int t(){return 4;} } //     /   \        /    \
class ABA : AB { virtual int t(){return 5;} } // AAA(2) AAB(3) ABA(5) ABB(6)
class ABB : AB { virtual int t(){return 6;} } //

(the order doesn't really matter: A::t could return 3 and AAB::t 0, for example.


Is it possible to let the compiler assign the indexes to my classes?

I think that CRTP could help me; something like:

class X : A, AssignFirstAvailableIndex< X > { }

but I'm not good enough with templates. How could I implement that AssignFirstAvailableIndex template class?

(of course the compiler can see all of the classes at compile time)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

魂ガ小子 2024-10-13 00:01:42

有一个标准方法可以实现您的需要。标准区域设置方面使用它来标识自己。考虑检查标准标头“区域设置”。

class Base {
  public:
  // Being constructed contains a new unique identifier
  class Id {
    // An id factory returns a sequence of nonnegative ints
    static int allocate() {
      static int total = 0;
      return total++;
    }
    int _local;
    public:
    Id(): _local(allocate()) {}
    int get() const {return _local;}
  };
  //Child classes should make this function return an id generated by Base::Id constructed as static member.
  virtual int id() const = 0;
};

class Child1{
  public:
  static const Base::Id _id; 
  virtual int id() { return _id.get(); }
};

class Child2 {
  public:
  static const Base::Id _id; 
  virtual int id() { return _id.get(); }
};

class Child3 {
  public:
  static const Base::Id _id; 
  virtual int id() { return _id.get(); }
};

静态成员可以在实现文件中初始化,或者被模板化以允许直接从标头实例化,或者被重构以进行延迟初始化。

There is a standard method to implement what you need. Standard locale facets use it to identify themselves. Consider examining standard header "locale".

class Base {
  public:
  // Being constructed contains a new unique identifier
  class Id {
    // An id factory returns a sequence of nonnegative ints
    static int allocate() {
      static int total = 0;
      return total++;
    }
    int _local;
    public:
    Id(): _local(allocate()) {}
    int get() const {return _local;}
  };
  //Child classes should make this function return an id generated by Base::Id constructed as static member.
  virtual int id() const = 0;
};

class Child1{
  public:
  static const Base::Id _id; 
  virtual int id() { return _id.get(); }
};

class Child2 {
  public:
  static const Base::Id _id; 
  virtual int id() { return _id.get(); }
};

class Child3 {
  public:
  static const Base::Id _id; 
  virtual int id() { return _id.get(); }
};

Static members might be initialized in implementation files or be templated to allow instantiation directly from headers or be refactored for lazy initialization.

情深已缘浅 2024-10-13 00:01:42

也许是这样的? (语法可能到处都是错误的,但你明白了)

class A {
  virtual int t() { return 0; }
}

template <bool AB>
class AX<AB>: A {
  virtual int t() { return AB ? 1 : 2; }
};

template <bool AB2>
template <bool AB>
class AXX<AB2>: AX<AB> {
  virtual int t() { return AX<AB>::t() * 2 + (AB2 ? 1 : 2); }
}

...但是说真的,只使用 RTTI。

Maybe something like this? (syntax is probably wrong all over the place, but you get the idea)

class A {
  virtual int t() { return 0; }
}

template <bool AB>
class AX<AB>: A {
  virtual int t() { return AB ? 1 : 2; }
};

template <bool AB2>
template <bool AB>
class AXX<AB2>: AX<AB> {
  virtual int t() { return AX<AB>::t() * 2 + (AB2 ? 1 : 2); }
}

... But seriously, please just use RTTI.

国产ˉ祖宗 2024-10-13 00:01:42

我认为除了使用枚举(我认为这是一种完全合理的方法)之外,没有任何方法可以在编译时生成索引。我不确定模板是否会有帮助,因为模板纯粹是功能性的,除了模板类型本身的名称之外,没有地方可以存储任何全局状态(即当前索引)(这正是您正在尝试的)以避免)。

如果您确实想要整数 ID,那么在运行时设置它们可能是最简单的方法,而不是太努力。

首先,拥有一个表示类型的对象,并使用典型的手工 RTTI 方法:让每个类都有该对象的静态实例,并使其虚拟 get-type-in​​fo 函数返回指向该对象的指针。因此,每个类中都会有一些代码,如下所示:

static TypeInfo ms_type_info;
virtual const TypeInfo *GetTypeInfo() const {
    return &ms_type_info;
}

并且您将定义类型信息,将任何信息放入 <<无论您想要什么信息>> 部分TypeInfo 存储起来以使其比编译器的 RTTI 更好;

TypeInfo WhateverClass::ms_type_info(<<whatever info you want>>);

(我见过的每个实现都使用宏来自动创建这两个文本位;有点难看,但您的选择是有限的,而且它比键入它要好。)

TypeInfo struct 本身看起来有点像这样:

struct TypeInfo {
    int type_index;
    TypeInfo *next;
    TypeInfo(<<whatever>>) {<<see below>>}
};

如果读者更喜欢 get 和 set 函数,它可以有这些。

TypeInfo 对象对于类应该是静态的,而不是函数,因为其目的是创建所有 TypeInfos 的列表。这里你有两个选择。自动的方法是让上面留空的构造函数中的每个 TypeInfo 将自身添加到某种全局链表中;另一种是拥有一个大功能,可以手动将所需的内容添加到全局列表中。

然后,在启动时,运行 TypeInfo 对象并为每个对象分配一个索引。假设有一个 TypeInfo *g_first_type_info 指向列表中的第一个类型信息,类似这样的事情就可以做到:

int next_type_index=0;
for(TypeInfo *ti=g_first_type_info;ti;ti=ti->next)
    ti->type_index=next_type_index++;

现在,当您需要整数 ID 时,您可以轻松检索它:

object->GetTypeInfo()->type_index;

您可以实现 < code>t 现在很容易:

virtual int t() const {
    return ms_type_info.type_index;
}

完全公开我能想到的问题:

  • 类型索引不是在编译时设置的,所以如果你构建数组,你需要在运行时构建它们。如果您的数组本来是编译时常量,那么这可能会浪费代码 - 但是对于不支持 C99 样式数组初始化语法的编译器,这有时会使代码更具可读性。

  • 如果您想在 main 启动之前在全局构造函数中完成所有操作,您可能会遇到麻烦,因为 TypeInfo 对象只是普通的全局对象(无论如何在这种情况下) ) 无论如何,在分配类型索引之前都无法正确使用。

  • 链接器倾向于删除似乎没有使用的全局对象,因此静态库中的类型信息对象可能永远不会将自己添加到列表中。因此,您应该记住,虽然自动注册方法运行良好,但当它不起作用时,它就不起作用,因此您最终可能不得不手动注册内容。

I don't think there's any way to generate the indexes at compile time, except by using an enum (which I'd consider a perfectly reasonable approach). I'm not sure that a template would help, because templates are purely functional and there's nowhere to store any global state (i.e., the current index) the except in the name of the templated type itself (which is exactly what you're trying to avoid).

If you really want integer IDs, it's probably easiest to set them up at runtime rather than trying too hard.

Firstly, have an object that represents a type, and use the typical hand-made-RTTI approach: have each class have a static instance of that object, and have its virtual get-type-info function return a pointer to that object. So each class would have a little bit of code in it, like this:

static TypeInfo ms_type_info;
virtual const TypeInfo *GetTypeInfo() const {
    return &ms_type_info;
}

And you'd define the type info, putting into the <<whatever you info you want>> section whatever info the TypeInfo stores off to make it better than the compiler's RTTI;

TypeInfo WhateverClass::ms_type_info(<<whatever info you want>>);

(Every implementation I've seen of this uses a macro to automate the creation of these two bits of text; a bit ugly, but your options are limited, and it's better than typing it out.)

The TypeInfo struct itself would look a bit like this:

struct TypeInfo {
    int type_index;
    TypeInfo *next;
    TypeInfo(<<whatever>>) {<<see below>>}
};

If the reader would prefer get and set functions, it could have those.

The TypeInfo object should be static to the class, not the function, because the intent is to create a list of all TypeInfos. You have two options here. The automatic one is to have each TypeInfo, in the constructor that was left blank above, add itself to some kind of global linked list; the other one is to have a big function that adds the ones it wants to the global list manually.

Then, on startup, run through the TypeInfo objects and assign each an index. Something like this would do, assuming that there's a TypeInfo *g_first_type_info that points to the first type info in the list:

int next_type_index=0;
for(TypeInfo *ti=g_first_type_info;ti;ti=ti->next)
    ti->type_index=next_type_index++;

Now, when you want your integer ID, you can retrieve it easily:

object->GetTypeInfo()->type_index;

You could implement t easily enough now:

virtual int t() const {
    return ms_type_info.type_index;
}

Full disclosure of the problems I can think of:

  • The type indexes aren't set at compile time, so if you build up arrays you'll need to build them at runtime. This can be a waste of code if your arrays would otherwise be compile time constants - however for compilers that don't support the C99-style array initialization syntax, this can sometimes make the code more readable.

  • If you like to do everything in global constructors before main starts, you may come unstuck, as the TypeInfo objects are just ordinary global objects and (in this situation anyway) aren't properly ready for use anyway until the type indexes have been assigned.

  • Linkers have a tendency to strip out global objects that don't appear to get used, so type info objects in static libraries may never add themselves to the list. So you should bear in mind that whilst the automatic registration approach works well, when it doesn't work, it doesn't, so you may end up having to register things manually anyway.

留蓝 2024-10-13 00:01:42

我已成功使用此处描述的方法:
http://www.flipcode.com/archives/Run_Time_Type_Information.shtml。基本上每个需要 RTTI 的类都有一个返回其 rtti 类型的静态函数。使用类型结构的优点是您可以向 rtti 结构添加函数。

我修改了此方法以允许诸如 component->IsOfType(CollisionComponent::GetClass()) 之类的操作。

我还扩展了 RTTI 结构以提供类名,现在我可以调用 component->IsOfType("CollisionComponent") 而无需包含 CollisionComponent.h。

当与这个动态类创建结合使用时,这种方法非常方便。我能够在脚本中创建和识别 C++ 类,并实例化大量不同的组件,这些组件仅在操作系统/硬件支持所需类型时才加载。我还可以根据服务器版本重新配置加载的组件类型。例如,一台服务器可以允许使用“CollisionComponent_V2_0”,而另一台服务器则强制使用“CollisionComponent_Proxy_V2”

I have been successfully using the method described here:
http://www.flipcode.com/archives/Run_Time_Type_Information.shtml. Basically each class which requires RTTI has a static function which returns its rtti type. the advantage of using a structure for the type is that you can add functions to your rtti structure.

I modified this approach to allow things like component->IsOfType(CollisionComponent::GetClass()).

I also extended the RTTI structure to provide a class name and I'm able now to call component->IsOfType("CollisionComponent") Without including CollisionComponent.h.

This approach is very handy when combined with this dynamic class creation. I'm able to create and identify C++ classes in scripts and instantiate a large number of different components which are only loaded when the OS/hardware supports the types needed. I'm also able to reconfigure the types of components loaded depending on the server version. For example a server can allow the use of "CollisionComponent_V2_0" while another one will force the use of "CollisionComponent_Proxy_V2"

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文