检查一个对象的类是不是派生自C++中另一个对象的类

Posted

技术标签:

【中文标题】检查一个对象的类是不是派生自C++中另一个对象的类【英文标题】:Check if class of one object is derived from class of another object in C++检查一个对象的类是否派生自C++中另一个对象的类 【发布时间】:2019-12-04 00:51:16 【问题描述】:

如果我得到对象ab,我如何确定a的类是否继承自b的类?


背景:我有一个 C++ 库,我想为它编写一个 Python 绑定生成器。该库提供了一组从公共基类派生的类。对于 Python 绑定,我需要所有类的函数列表。我通过nm -D myLibrary.so 获得了一个方法列表,但缺少继承的类的方法,例如风格

template<class WrappedClass>
class Wrapper: public WrappedClass 
  public:
    // ...
;

typedef Wrapper<ClassA> ClassB;

。所以我得到了像ClassA 这样的类的所有功能,并且只想知道它们属于哪个ClassB

我可以在运行时从库中获取所有可用类的名称列表,并且可以通过工厂函数获取具有这些类型的对象,该工厂函数接受类名并提供该类型的对象。所以最后一块是动态确定像ClassB这样的类属于像ClassA这样的类。所以这个问题。

PS:原则上,我可以编写一个 C++ 代码生成器来生成测试代码,然后针对库进行编译以确定哪些类从其他类继承。这将导致无法将我的代码与库一起编译但需要第二次编译的高成本。由于除了这个问题之外,我几乎可以解决所有问题,因此我非常希望有一个不同的解决方案。


PPS:有人告诉我在 cmets 中使用 decltype。这不起作用:

#include <iostream>
#include <type_traits>

struct A ;
struct B : A ;

int main() 
    A* a = new A();
    A* b = new B();
  std::cout << std::boolalpha;
  std::cout << "A, B: " << std::is_base_of<A,B>::value << std::endl;
  std::cout << "a, b: " << std::is_base_of<decltype(a),decltype(b)>::value << std::endl;
  std::cout << "*a, *b: " << std::is_base_of<decltype(*a),decltype(*b)>::value << std::endl;
  std::cout << "a., B: " << std::is_base_of<decltype(a),B>::value << std::endl;
  std::cout << "*a., B: " << std::is_base_of<decltype(*a),B>::value << std::endl;
  return 0;


产量

A, B: true
a, b: false
*a, *b: false
a., B: false
*a., B: false

PPPS:建议使用一个答案

std::is_base_of_v<std::decay_t<decltype(*a)>, std::decay_t<decltype(*b)>>

。我无法让它工作:

#include <iostream>
#include <type_traits>

struct A ;
struct B : A ;

int main() 
    A* a = new A();
    A* b = new B();
  std::cout << std::boolalpha;
  std::cout << "A, B: " << std::is_base_of<A,B>::value << std::endl;
  std::cout << "B, A: " << std::is_base_of<B,A>::value << std::endl;
  std::cout << "*a, *b: " << std::is_base_of<std::decay_t<decltype(*a)>, std::decay_t<decltype(*b)>>::value << std::endl;
  std::cout << "*b, *a: " << std::is_base_of<std::decay_t<decltype(*b)>, std::decay_t<decltype(*a)>>::value << std::endl;
  return 0;

产量:

A, B: true
B, A: false
*a, *b: true
*b, *a: true

【问题讨论】:

你在找这个吗:en.cppreference.com/w/cpp/types/is_base_of @JerryJeremiah 据我了解,std::is_base_of 需要传递两个类,我只有两个对象。 @2xB "我可以在运行时从库中获取所有可用类的名称列表" 你可以吗?你会展示你是如何做到的吗?这是不可能直接使用标准库的,但如果你能够枚举类型,那么应该可以做点什么。 @2xB 你可以使用decltype 我认为typeiddecltypeis_base_of 的组合应该是可能的。可以遍历所有对并测试它们,但这是一个粗略的想法。 【参考方案1】:

如果我得到对象a和b,我如何确定a的类是否继承自b的类?

回答问题,std::is_base_of

std::is_base_of_v<std::decay_t<decltype(*a)>, std::decay_t<decltype(*b)>>

在您的示例中,ab 实际上不是对象,而是指向对象的指针,因此您必须取消引用才能获取对对象的引用,使用 decltype 获取类型,然后删除引用和使用 std::decay_t 进行 const 限定。

在 C++ 中,对象既有静态类型又有动态类型。 std::is_base_of 适用于静态类型。对于动态类型,dynamic_cast 会告诉您类型是否相关,但没有标准查询仅回答一个动态类型是否是另一个动态类型的基础。对于另一种语言的包装,静态类型似乎是更有趣的类型,因为它可以调用成员函数。

但是,您解决实际问题的方法存在缺陷。使用nm 来确定类的成员函数名称不会告诉您这些名称的可访问性或如何解决重载。 std::is_base_of 将回答所提出的问题,但如果基础不可访问或不明确,它将返回 true,在这种情况下,基础的成员函数不会直接应用于派生。

假设您不想手动维护绑定,最好的方法是找到可以生成它们的工具(例如 swig)或编写这样的工具(例如使用 python clang 绑定)。

【讨论】:

由于您的答案仅适用于静态类型,因此我的问题并未解决。我在原始问题中添加了另一条评论(PPPS)来演示该问题。 这是真的。我确实回答说动态类型无法移植。但是,(a)我认为静态类型可能是您在包装时想要的。如果你有一个用其他语言包装的 A,那么即使动态类型是 B,能够在其上调用 B 的方法也会很奇怪。在 python 中,一个人创建一个 B,并将其用作 B,并且任何需要 A 的包装函数无论如何都会将其用作 A。并且 (b) 我认为不管静态/动态类型差异如何,由于上述原因,这不是一种强大的包装方法。 哦,对不起,我不想问任何关于包装的问题。我想确定哪个类继承自哪个其他类,所以我知道哪个类具有从其他类继承的哪些功能,仅此而已。不同之处在于,我只在运行时获取我想知道其继承的类的名称,并且我有一个方法可以在运行时生成这些类的对象,如果我提供它们的名称。 但是python clang 绑定听起来很有趣,我一定会研究它们的!【参考方案2】:

我找到的一个解决方案是使用 Itanium ABI。它主要取自 https://***.com/a/11675891/8575607 。请注意,如果父类没有虚拟成员,这将不起作用(尽管我不知道为什么)。

所以这个:

#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;


class foo virtual void f1();
class bar : public foo virtual void f2();
class abc : public bar virtual void f3();

int main() 
  foo* myfoo = new foo();
  foo* mybar = new bar();
  foo* myabc = new abc();
  std::cout << std::boolalpha;
  std::cout << typeid(*mybar).name() << " " << typeid(*myfoo).name() << "\n";
  std::cout << is_ancestor(typeid(*mybar), typeid(*myfoo)) << "\n";
  std::cout << is_ancestor(typeid(*myfoo), typeid(*mybar)) << "\n";
  std::cout << is_ancestor(typeid(*myfoo), typeid(*myabc)) << "\n";

按预期产生此输出:

3bar 3foo
false
true
true

【讨论】:

如果您的对象没有 vtable,这将不起作用。 vtables 通常只为具有虚拟成员的对象发出(因为它们仅用于解析虚拟调用)。

以上是关于检查一个对象的类是不是派生自C++中另一个对象的类的主要内容,如果未能解决你的问题,请参考以下文章

C++ 包含两个派生自同一个基类的类

继承&派生

C++继承

Visual Studio 和使用 C++ 中的类工厂自动创建对象

在具有 C++ 中另一个类型的类中声明属性 [重复]

python面向对象之继承与派生