在运行时检查仅具有类型之一和void *的继承

时间:2018-10-18 05:51:59

标签: c++

我需要一种在运行时检查类型是否通过继承与匿名对象的类型相关的方法,该对象是我的空指针。

假设我有两种类型:

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相同。

换句话说,当前的typetag实现与此类似:

template <typename T>
void *getTypeTag();

template <>
void *getTypeTag<Base>()
{
  return 1;
}

template <>
void *getTypeTag<Derived>()
{
  return 2;
}

我可以在typetag中存储什么以检查继承关系?

我正在寻求可扩展的解决方案,因为将有许多具有频繁继承关系的类。

编辑/说明:

  • 我正在使用第三方API,这只会让我无效 指针,因此基于类型obj的重载isRelated不是 可能
  • 无法进行动态类型转换,因为它们在T和void *之间不起作用

2 个答案:

答案 0 :(得分:3)

我添加了另一个答案,该答案不依赖于未定义的行为。


警告:以下答案取决于未定义的行为

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,我们假设还要再添加一个“ Unrelated”类:

// 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分钟之内对我的原始问题投了赞成票,那是小事,而对投票的帮助会比提供帮助更快乐。

答案 1 :(得分:1)

我最初编辑了第一个答案,但是结果太长了。

这是第二种解决方案,该解决方案不依赖于未定义的行为(或者不应该这样做,除非我犯了一个错误)。

此解决方案也有局限性,但不依赖于未定义的行为。

限制

  • 不支持多重继承
  • 仅支持上播(下播支持非常容易, 添加)

乍一看,该实现可能看起来有些奇怪,但相对简单。许多事情可以做的不同,可以增加一些早期工作,更好的组织,不同的结构等。

简要说明

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

这将通过第一个答案底部的原始测试,以及未公开的其他测试。