确定类型擦除类之间的继承关系
Determining inheritance relationships among type-erased classes
我有一个典型的类型删除设置:
struct TEBase
{
virtual ~TEBase() {}
// ...
};
template <typename T>
struct TEImpl : TEBase
{
// ...
};
现在的问题是:给定像这样的第二个类层次结构,
struct Foo { };
struct Bar : Foo { };
struct Unrelated { };
给定一个TEBase * p
,是否可以确定*p
的动态类型是否为形式TEImpl<X>
,其中X
由Foo
衍生而来?换句话说,我想要function:
template <typename T> bool is_derived_from(TEBase * p);
这样:
is_derived_from<Foo>(new TEImpl<Foo>) == true
is_derived_from<Foo>(new TEImpl<Bar>) == true
is_derived_from<Foo>(new TEImpl<Unrelated>) == false
特别是,我正在寻找一种通用的、非侵入性的、高效的解决方案。我找到了两个解决这个问题的方法(贴在下面作为答案),但它们都不能满足所有三个标准。
这样的:
template <typename Type, typename UnaryPredicate>
void DoPred(UnaryPredicate pred)
{
if (T * p = dynamic_cast<Derived<T> *>(this))
{
return pred(p->type);
}
return false;
}
这不是100%通用的,因为您不能,例如,说DoPred<int>
。更通用的解决方案是向层次结构中添加virtual std::type_info type() const { return typeid(...); }
成员函数,并使用来确定类型是否匹配(标准类型擦除习惯用法)。不过,这两种方法都使用相同类型的RTTI。
澄清后:
现在,我不认为这个问题可以解决。你所拥有的只是一个TEBase
子对象。它可以是TEImpl<Bar>
的一部分,也可以是TEImpl<Unrelated>
的一部分,但这两种类型都与TEImpl<Foo>
无关,而这正是您所追求的。
你实际上是在问TEImpl<Bar>
从TEImpl<Foo>
派生出。要做到这一点,您实际上希望TEImpl<T>
从所有TEImpl<std::direct_bases<T>::type>...
继承,如果您明白我的意思的话。这在c++ 11中是不可能的,但在TR2中是可能的。GCC已经支持它了。下面是一个示例实现。(它会由于基的不明确而产生警告,这可以通过更多的工作来避免,但它仍然可以工作。)
#include <tr2/type_traits>
struct TEBase { virtual ~TEBase() {} };
template <typename T> struct TEImpl;
template <typename TL> struct Derivator;
template <typename TL, bool EmptyTL>
struct DerivatorImpl;
template <typename TL>
struct DerivatorImpl<TL, true>
: TEBase
{ };
template <typename TL>
struct DerivatorImpl<TL, false>
: TEImpl<typename TL::first::type>
, Derivator<typename TL::rest::type>
{ };
template <typename TL>
struct Derivator
: DerivatorImpl<TL, TL::empty::value>
{ };
template <typename T>
struct TEImpl
: Derivator<typename std::tr2::direct_bases<T>::type>
{
};
template <typename T>
bool is(TEBase const * b)
{
return nullptr != dynamic_cast<TEImpl<T> const *>(b);
}
struct Foo {};
struct Bar : Foo {};
struct Unrelated {};
#include <iostream>
#include <iomanip>
int main()
{
TEImpl<int> x;
TEImpl<Unrelated> y;
TEImpl<Bar> z;
TEImpl<Foo> c;
std::cout << std::boolalpha << "int ?< Foo: " << is<Foo>(&x) << "n";
std::cout << std::boolalpha << "Unr ?< Foo: " << is<Foo>(&y) << "n";
std::cout << std::boolalpha << "Bar ?< Foo: " << is<Foo>(&z) << "n";
std::cout << std::boolalpha << "Foo ?< Foo: " << is<Foo>(&c) << "n";
}
我建议阅读文章泛型编程:类型列表和应用程序。在那里,Andrei Alexandrescu描述了一个临时访问者的实现,它应该可以解决你的问题。另一个很好的资源是他的书《现代c++设计》,他在书中描述了一个使用相同方法的蛮力方式的多调度程序(第265页…)。
在我看来,这两个资源比任何可以打印在这里的代码更好地理解。
这个解决方案涉及到滥用异常。如果TEImpl
类型只是抛出它的数据,is_derived_from
可以捕获它正在寻找的类型。
struct TEBase
{
virtual ~TEBase() {}
virtual void throw_data() = 0;
};
template <typename T>
struct TEImpl : public TEBase
{
void throw_data() {
throw &data;
}
T data;
};
template <typename T>
bool is_derived_from(TEBase* p)
{
try {
p->throw_data();
} catch (T*) {
return true;
} catch (...) {
// Do nothing
}
return false;
}
这个解决方案非常有效。它可以完美地与任何继承结构一起工作,并且完全是非侵入性的。
唯一的问题是它根本没有效率。异常并不打算以这种方式使用,而且我怀疑这种解决方案比其他解决方案慢数千倍。
这个解决方案涉及比较typeid
s。TEImpl
知道自己的类型,所以它可以根据自己的类型来检查传递的typeid
。
问题是,这种技术在添加继承时不起作用,所以我还使用模板元编程来检查类型是否定义了typedef super
,在这种情况下,它将递归地检查它的父类。
struct TEBase
{
virtual ~TEBase() {}
virtual bool is_type(const type_info& ti) = 0;
};
template <typename T>
struct TEImpl : public TEBase
{
bool is_type(const type_info& ti) {
return is_type_impl<T>(ti);
}
template <typename Haystack>
static bool is_type_impl(const type_info& ti) {
return is_type_super<Haystack>(ti, nullptr);
}
template <typename Haystack>
static bool is_type_super(const type_info& ti, typename Haystack::super*) {
if(typeid( Haystack ) == ti) return true;
return is_type_impl<typename Haystack::super>(ti);
}
template <typename Haystack>
static bool is_type_super(const type_info& ti, ...) {
return typeid(Haystack) == ti;
}
};
template <typename T>
bool is_derived_from(TEBase* p)
{
return p->is_type(typeid( T ));
}
为使其正常工作,Bar
需要被重新定义为:
struct Bar : public Foo
{
typedef Foo super;
};
这应该是相当有效的,但它显然不是非侵入性的,因为无论何时使用继承,它都需要在目标类中使用typedef super
。typedef super
还必须是可公开访问的,这违背了许多人认为将typedef super
放在私有部分的推荐做法。
它也根本不处理多重继承。
更新:此解决方案可以进一步使其通用且非侵入性。
通用
typedef super
很好,因为它是习惯用法,并且已经在许多类中使用,但是它不允许多重继承。为了做到这一点,我们需要将其替换为可以存储多种类型的类型,例如元组。
如果Bar
被改写为:
struct Bar : public Foo, public Baz
{
typedef tuple<Foo, Baz> supers;
};
我们可以通过向TEImpl中添加以下代码来支持这种形式的声明:template <typename Haystack>
static bool is_type_impl(const type_info& ti) {
// Redefined to call is_type_supers instead of is_type_super
return is_type_supers<Haystack>(ti, nullptr);
}
template <typename Haystack>
static bool is_type_supers(const type_info& ti, typename Haystack::supers*) {
return IsTypeTuple<typename Haystack::supers, tuple_size<typename Haystack::supers>::value>::match(ti);
}
template <typename Haystack>
static bool is_type_supers(const type_info& ti, ...) {
return is_type_super<Haystack>(ti, nullptr);
}
template <typename Haystack, size_t N>
struct IsTypeTuple
{
static bool match(const type_info& ti) {
if(is_type_impl<typename tuple_element< N-1, Haystack >::type>( ti )) return true;
return IsTypeTuple<Haystack, N-1>::match(ti);
}
};
template <typename Haystack>
struct IsTypeTuple<Haystack, 0>
{
static bool match(const type_info& ti) { return false; }
};
非侵入性
现在我们有了一个高效和通用的解决方案,但它仍然是侵入性的,所以它不支持不能修改的类。
为了支持这一点,我们需要一种从类外部声明对象继承的方法。对于Foo
,我们可以这样做:
template <>
struct ClassHierarchy<Bar>
{
typedef tuple<Foo, Baz> supers;
};
要支持这种样式,首先我们需要非专门化形式的ClassHierarchy,我们将这样定义它:
template <typename T> struct ClassHierarchy { typedef bool undefined; };
我们将使用undefined
的存在来判断类是否已经被专门化。
现在我们需要向TEImpl添加更多的函数。我们仍然会重用前面的大部分代码,但是现在我们还将支持从ClassHierarchy
读取类型数据。
template <typename Haystack>
static bool is_type_impl(const type_info& ti) {
// Redefined to call is_type_external instead of is_type_supers.
return is_type_external<Haystack>(ti, nullptr);
}
template <typename Haystack>
static bool is_type_external(const type_info& ti, typename ClassHierarchy<Haystack>::undefined*) {
return is_type_supers<Haystack>(ti, nullptr);
}
template <typename Haystack>
static bool is_type_external(const type_info& ti, ...) {
return is_type_supers<ClassHierarchy< Haystack >>(ti, nullptr);
}
template <typename Haystack>
struct ActualType
{
typedef Haystack type;
};
template <typename Haystack>
struct ActualType<ClassHierarchy< Haystack >>
{
typedef Haystack type;
};
template <typename Haystack>
static bool is_type_super(const type_info& ti, ...) {
// Redefined to reference ActualType
return typeid(typename ActualType<Haystack>::type) == ti;
}
现在我们有了一个高效、通用、非侵入性的解决方案。
未来解决方案此解决方案符合标准,但是必须显式地记录类层次结构仍然有点烦人。编译器已经知道关于类层次结构的所有信息,所以我们不得不做这些单调乏味的工作,这是很遗憾的。
对于这个问题的一个建议的解决方案是N2965:类型特征和基类,它已经在GCC中实现了。本文定义了一个direct_bases
类,它与我们的ClassHierarchy
类几乎相同,除了它的唯一元素type
保证是一个元组,就像supers
一样,并且该类完全由编译器生成。
因此,现在我们必须编写一个小样板来使其工作,但如果N2965被接受,我们可以摆脱样板并使TEImpl更短。
特别感谢Kerrek SB和Jan Herrmann。这个回答从他们的评论中得到了很多启发。
- 继承函数的重载解析
- 继承期间显示未知行为的子类
- 头文件-继承c++
- 为什么在保护模式下继承升级不起作用
- 通过继承类使用来自不同命名空间的运算符
- 子目录是否继承属性,例如add_definitions,include_directories和父Cmakelist.t
- 混合组合和继承的C++问题
- 继承:构造函数,初始化C++11中基类的类C数组成员
- 当依赖关系和依赖关系都是多态时,在哪个继承级别存储依赖关系指针?
- has-a关系的继承
- C++ 项目设计 - 私有继承和"is a"关系
- 继承自 HAS-A 关系
- C++中组合关系的继承
- 如何在单独的 Boost.Python 模块之间拆分继承关系
- 确定类型擦除类之间的继承关系
- 模板可以用来检测继承关系吗?
- 为什么shared_ptr无法解析函数接口中的继承关系?
- 具有继承模板类的循环依赖关系
- 基于继承关系的模板友谊
- 类之间的关系——继承和组合