在运行时检查继承是否只有一种类型和 void*
Check for inheritance at runtime having only one of the types and void*
我需要一种方法在运行时检查类型是否通过继承与匿名对象的类型相关,对于我的目的来说,void 指针。
假设我有两种类型:
class Base
{
public:
virtual ~Base();
};
class Derived : public Base
{
public:
~Derived();
};
Base *base = new Derived();
Derived *derived = new Derived();
我需要实现以下功能:
template <typename T>
T *isRelated(void *obj, void *tag)
{
//If T is related to obj's real type (established based on tag value)
// return reinterpret_cast<T*>(obj);
//else
// return nullptr;
}
目前,我可以通过在 typetag 中存储任何一个值(在编译时确定并作为值返回(并比较相等性来检查 obj 是否与 T 相同。
换句话说,当前的类型标记实现类似于以下内容:
template <typename T>
void *getTypeTag();
template <>
void *getTypeTag<Base>()
{
return 1;
}
template <>
void *getTypeTag<Derived>()
{
return 2;
}
我可以在类型标记中存储什么来检查继承关系?
我正在寻找一个可扩展的解决方案,因为会有许多具有频繁继承关系的类。
编辑/澄清:
- 我正在使用只会给我无效的第三方 API 指针,因此重载是基于类型化的 obj 不是相关的 可能
- 动态强制转换是不可能的,因为它们在 T 和 void 之间不起作用*
我添加了另一个答案,它不依赖于未定义的行为。
警告:以下答案取决于未定义的行为
C++14 标准: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf
第10章,第5节:
基类子对象的分配顺序 派生对象 (1.8( 未指定
按照这个逻辑,我的回答中的reinterpret_cast
将依赖于未定义的行为来工作。此外,考虑到正在转换void*
- 似乎无法在不知道原始类型的情况下将其安全地转换为基类。
提出了一种可能的解决方案,效果很好 - 但它在以下方面受到限制:
- 我必须使用反映原始类型继承的继承模式专门化TypeTagSpec
- 验证发生在类型层上,而不是对象指针层上(这在最后一段中有所阐明(
考虑到人为的示例类 Base 和 Derived,让我们假设我们将再添加一个类"不相关":
// Some class which is not related to our inheritance check, for testing later
class Unrelated
{
public:
virtual ~Unrelated(){}
};
现在,后续的专用 TypeTagSpec 结构将被实例化并作为 void *tag 传递给某个 API:
// This is the base class, and we will initially cast all void *tag objects to this type
struct TypeTag
{
virtual ~TypeTag(){}
};
// Unspecialized template derives from TypeTag, to give us a way 'up' the inheritance
template <typename T>
struct TypeTagSpec : TypeTag
{
virtual ~TypeTagSpec(){}
};
// Specialized testing typetag for Unrelated type, used for testing
template <>
struct TypeTagSpec<Unrelated> : TypeTag
{
virtual ~TypeTagSpec(){}
};
// Specialized Base typetag, nothing special
template <>
struct TypeTagSpec<Base> : TypeTag
{
virtual ~TypeTagSpec(){}
};
// Magic here - specialized tagtype for Derived actually inherits from Base typetag, giving a clear inheritance line
template <>
struct TypeTagSpec<Derived> : TypeTagSpec<Base>
{
virtual ~TypeTagSpec(){}
};
这给我们留下了代码:
// The solution
template <typename T>
T *isRelated(void *obj, void *tag)
{
const TypeTag *typetag = reinterpret_cast<TypeTag*>(tag);
if(dynamic_cast<const TypeTagSpec<T>*>(typetag))
return reinterpret_cast<T*>(obj);
return nullptr;
}
TEST(TypeTag, Inheritance)
{
Derived *derived = new Derived();
TypeTag *typetag = new TypeTagSpec<Derived>();
// Test begins here
void *obj = derived;
void *tag = typetag;
EXPECT_EQ(isRelated<Base>(obj, tag), derived);
EXPECT_EQ(isRelated<Derived>(obj, tag), derived);
EXPECT_EQ(isRelated<Unrelated>(obj, tag), nullptr);
// Test ends here
delete derived;
delete typetag;
}
请务必注意,问题是查找类型是否相关,因此此测试将通过:
auto *base = new Base();
auto *tag = new TypeTagSpec<Base>();
EXPECT_EQ(isRelated<Derived>(base, tag), base);
在此测试中,isRelated 仍将返回指向 base 的指针;实际上,此指针可能并不总是有效/可用的,具体取决于要转换的类。由于我的用例涉及向上转换类型层关系检查,因此我不关心这种对象有效性的细微差别 - 并且从类型角度(而不是对象角度(来看,这种关系在技术上仍然有效。
旁注: 谁在提出我最初的问题后 5 分钟内对我的问题投了反对票,那就是小事,而且比帮助我更快乐。
我最初编辑了我的第一个答案,但最终太长了。
这是第二个解决方案,这个解决方案不依赖于未定义的行为(或者不应该,除非我犯了错误(。
此解决方案也有局限性,但不依赖于未定义的行为。
限制:
- 不支持多重继承
- 仅支持向上转换(向下转换支持相当容易 添加(
实现乍一看可能看起来有点奇怪,但相对简单。许多事情可以以不同的方式完成,可以添加一些早期退出,更好的组织,不同的结构等。
简要说明:
Base TypeTag实现了一个虚拟方法,它获取我们想要的 Type 的 typeid,并将其一直带回顶级 TypeTagSpec。从那里,我们开始通过TypeTagCastPropagator进行递归下降,每一步执行干净的强制转换,直到找到并返回所需的类型对象。
如果在继承链中找不到所需的类型,则返回 nullptr。
如果所需的类型甚至不是 TypeTag 下的专用类型,则返回 nullptr。
基类型,虚拟方法用于将所需的 typeid 带回分配的原始类型:
// This is what gets stored in void *tag
struct TypeTag
{
virtual ~TypeTag(){}
virtual void *isRelated(void *obj,
const std::type_info &type_desired) const = 0;
};
// Unspecialized implementation, waiting to be specialized
template <typename T>
struct TypeTagSpec : TypeTag
{
virtual ~TypeTagSpec(){}
virtual void *isRelated(void *obj,
const std::type_info &type_desired) const override = 0;
};
现在,TypeTagCastPropagator 实现 - 这将在继承层次结构中执行递归下降。它有两种可能的情况: - 达到最终基本类型 - 达到中间基类型
template <typename D>
struct TypeTagCastPropagator
{
// Non-propagating version, base -> base (no further casts possible)
template <typename B,
typename std::enable_if<std::is_same<B, D>::value>::type* = nullptr>
void *castDown(const TypeTagSpec<D> *self, void *obj,
const std::type_info &desired) const
{
D *derived = reinterpret_cast<D*>(obj);
if(typeid(D) == desired)
return obj;
return nullptr;
}
// Propagating version, derived -> base
template <typename B,
typename std::enable_if<std::is_base_of<B, D>::value &&
!std::is_same<B, D>::value>::type* = nullptr>
void *castDown(const TypeTagSpec<D> *self, void *obj,
const std::type_info &desired) const
{
// Check to see if we are dealing with the desired type
D *derived = reinterpret_cast<D*>(obj);
if(typeid(D) == desired)
return obj;
// Attempt to upcast
B *base = dynamic_cast<B*>(derived);
const auto *tag = dynamic_cast<const TypeTagSpec<B>*>(self);
if(base && tag)
return tag->propagator.template castDown<B>(tag, base, desired);
return nullptr;
}
};
最后,标签专业化。这些是足够通用的,可以将它们放置在宏中并专门用于任何类型。
template <>
struct TypeTagSpec<Base> : TypeTag
{
TypeTagCastPropagator<Base> propagator;
virtual void *isRelated(void *obj,
const std::type_info &desired) const override
{
// Base->Base indicates that there are no more meaningful inheritance checks to make
return propagator.castDown<Base>(this, obj, desired);
}
};
template <>
struct TypeTagSpec<Derived> : TypeTagSpec<Base>
{
TypeTagCastPropagator<Derived> propagator;
virtual void *isRelated(void *obj,
const std::type_info &desired) const override
{
return propagator.castDown<Base>(this, obj, desired);
}
};
最后一个功能:
template <typename T>
T *isRelated(void *obj, void *tag)
{
const TypeTag *typetag = reinterpret_cast<TypeTag*>(tag);
void *result = typetag->isRelated(obj, typeid(T));
if(result)
return reinterpret_cast<T*>(obj);
return nullptr;
}
这通过了第一个答案底部的原始测试,以及未公开的其他测试。
- 在运行时检查继承是否只有一种类型和 void*
- 另一种类型的智能ptr,比如具有弱refs的unique_ptr
- void* 数组将元素转换为另一种类型
- 将一种类型的比特重新解释为不同类型的比特的技术
- 为什么需要类型名称,即使似乎足以推断名称应该是一种类型?
- 在 c++ 中将一种结构类型分配给另一种类型
- 将空基类优化对象强制转换为另一种类型是否会破坏严格的别名?
- 我怎样才能让编译器推导出一种类型的 nullptr
- 一种类型的多个 cv 分解
- 如何将对象定义为一种类型,然后再将其声明为子类型
- 类中的结构不是一种类型
- 一种类型特征,标识哪个类提供通过重载解析选择的函数
- 将 32 位浮点数和不强制转换的 32 位整数与双精度进行比较,当其中一个值可能太大而无法完全适合另一种类型时
- constexpr检查一种类型
- 何时与另一种类型兼容
- 依赖名称不是一种类型,带有 SFINAE 的模板
- 自动将一种 c++ 类型转换为另一种
- C 模板可以匹配(仅)一种类型的列表或另一种类型的列表
- 是否可以将一种类型的数组转换为大小不同的另一种类型的数组?
- 在比较中使用已强制转换为另一种类型的函数指针是否定义良好?