19

如果有的话,有什么 C++ 结构可以在运行时列出类的祖先?

基本上,我有一个类,它存储指向任何对象的指针,可能包括原始类型(有点像boost::any,我不想使用它,因为我需要保留对象的所有权)。在内部,这个指针是 a void*,但这个类的目标是void*用运行时类型安全包装 。赋值运算符是模板化的,所以在赋值时我获取typeid()传入指针的 并存储它。然后当我稍后再转换时,我可以检查typeid()转换类型与存储的type_info. 如果不匹配,强制转换将引发异常。

但是有一个问题:似乎我失去了多态性。假设BD. 如果我在我的类中存储一个指针D,那么存储的type_info也将是D. 然后稍后,我可能想要检索一个B指针。如果我使用我的类的方法强制转换为B*,那么会typeid(B) == typeid(D)失败,并且强制转换会引发异常,即使D->B转换是安全的。Dynamic_cast<>()不适用于这里,因为我在 avoid*而不是 or 的祖先上B操作D

我想做的是检查is_ancestor(typeid(B), typeid(D))这可能吗?(这不是dynamic_cast<>在幕后做的吗?)

如果没有,那么无论如何我都在考虑采用第二种方法:实现 aa class TypeInfo,其派生类是模板化的单例。然后我可以在这些类中存储我喜欢的任何信息,然后在我的类中保留指向它们的指针AnyPointer。这将允许我在编译时以更易于访问的方式生成/存储祖先信息。所以失败的选项#1(一种列出祖先的内置方式,只给出运行时可用的信息),是否有我可以使用的构造/过程,它允许在编译时自动生成和存储祖先信息,最好没有必须明确输入“类A派生自BCC派生自D“等等?一旦我有了这个,有没有一种安全的方式来实际执行那个演员?

4

5 回答 5

12

我有一个类似的问题,我通过异常解决了!我为此写了一篇文章:

第 1部分、第 2 部分代码

行。按照彼得的建议,这个想法的大纲如下。它依赖于这样一个事实,即如果D派生自B并抛出一个指向的指针,那么将激活D一个期望指向的指针的 catch 子句。B

然后可以编写一个类(在我的文章中我称它为any_ptr),其模板构造函数接受 aT*并将其副本存储为void*. 该类实现了一种将 静态void*转换为其原始类型T*并抛出结果的机制。U*一个期望where U= Tor Uis a base of的 catch 子句T将被激活,这个策略是在原始问题中实现测试的关键。

编辑:( 由 Matthieu M. 回答最好是独立的,请参阅 Dobbs 博士的完整答案)

class any_ptr {

    void* ptr_;
    void (*thr_)(void*);

    template <typename T>
    static void thrower(void* ptr) { throw static_cast<T*>(ptr); }

public:

    template <typename T>
    any_ptr(T* ptr) : ptr_(ptr), thr_(&thrower<T>) {}

    template <typename U>
    U* cast() const {
        try { thr_(ptr_); }
        catch (U* ptr) { return ptr; }
        catch (...) {}
        return 0;
    }
};
于 2012-01-08T18:09:50.343 回答
5

信息(通常)在实现中。虽然没有标准的 C++ 方法来访问它,但它没有公开。如果您愿意将自己绑定到特定的实现或实现集,您可以玩一个肮脏的游戏来查找信息。

使用 Itanium ABI 的 gcc 示例如下:

#include <cassert>
#include <typeinfo>
#include <cxxabi.h>
#include <iostream>

bool is_ancestor(const std::type_info& a, const std::type_info& b);

namespace {
  bool walk_tree(const __cxxabiv1::__si_class_type_info *si, const std::type_info& a) {
    return si->__base_type == &a ? true : is_ancestor(a, *si->__base_type);
  }

  bool walk_tree(const __cxxabiv1::__vmi_class_type_info *mi, const std::type_info& a) {
    for (unsigned int i = 0; i < mi->__base_count; ++i) {
      if (is_ancestor(a, *mi->__base_info[i].__base_type))
        return true;
    }
    return false;
  }
}

bool is_ancestor(const std::type_info& a, const std::type_info& b) {
  if (a==b)
    return true;
  const __cxxabiv1::__si_class_type_info *si = dynamic_cast<const __cxxabiv1::__si_class_type_info*>(&b);
  if (si)
    return walk_tree(si, a);
  const __cxxabiv1::__vmi_class_type_info *mi = dynamic_cast<const __cxxabiv1::__vmi_class_type_info*>(&b);
  if (mi)
    return walk_tree(mi, a);
  return false;
}

struct foo {};

struct bar : foo {};

struct baz {};

struct crazy : virtual foo, virtual bar, virtual baz {};

int main() {
  std::cout << is_ancestor(typeid(foo), typeid(bar)) << "\n";
  std::cout << is_ancestor(typeid(foo), typeid(baz)) << "\n";
  std::cout << is_ancestor(typeid(foo), typeid(int)) << "\n";
  std::cout << is_ancestor(typeid(foo), typeid(crazy)) << "\n";
}

我将其type_info转换为内部使用的真实类型,然后递归地使用它来遍历继承树。

我不建议在实际代码中执行此操作,但作为实现细节的练习,这并非不可能。

于 2012-07-26T18:39:29.433 回答
4

首先,您所要求的不能type_info.

在 C++ 中,对于从一个对象到另一个对象的强制转换,您不仅需要盲目地假设一个类型可以用作另一个类型,还需要调整指针,因为多重继承(编译时偏移)和虚拟继承(运行时偏移)。

将值从一种类型安全地转换为另一种类型的唯一static_cast方法是使用(适用于单继承或多继承)和dynamic_cast(也适用于虚拟继承并实际检查运行时值)。

不幸的是,这实际上与类型擦除不兼容(旧的template-virtual不兼容)。

如果您将自己限制为非虚拟继承,我认为应该可以通过将转换的偏移量存储到某些Configuration数据(您正在谈论的单例)中的各种基数来实现这一点。

对于虚拟继承,我只能想到一对type_info到 a的映射void* (*caster)(void*)

所有这些都需要手动枚举可能的演员表:(

于 2011-08-09T12:50:25.827 回答
1

无法使用std::type_info,因为它不提供查询继承信息或将std::type_info对象转换为其相应类型以便您进行强制转换的方法。

如果您确实有所有可能类型的列表,则需要存储在您的any对象使用boost::variant及其访问者中。

于 2011-08-09T08:38:16.633 回答
0

虽然我想不出任何方法来实现选项 #1,但如果您可以生成要使用的类的编译时列表,选项 #2 应该是可行的。使用 boost::MPL 和is_base_of元函数过滤此类型列表,以获得有效转换类型 ID 的列表,可以将其与保存的类型 ID 进行比较。

于 2011-08-09T08:26:46.150 回答