在c ++中使用RTTI将对象强制转换为正确的类型

时间:2012-12-06 20:50:32

标签: c++ rtti type-erasure dynamic-cast

我正试图找出一种方法,在一组有些困难的条件下动态地将子类的实例强制转换为其父类。

具体来说,我有一个对象层次结构看起来像(我已经简化了很多,所以如果有些事情没有意义,可能是由于简化):

class Object {
public:
  virtual ~Object() {}
};

// shown just to give an idea of how Object is used
class IntObject: public Object {
protected:
  int value;
public:
  IntObject(int v) { value = v; }
  int getValue() { return value; }
};

template <class T>
class ObjectProxy: public Object {
protected:
  T *instance;
public:
   ObjectProxy(T *instance): instance(instance) {}  
   T *getInstance() { return instance; }
};

ObjectProxy类本质上充当包装器,允许在Object层次结构中使用其他类型。具体来说,它允许保留指向类实例的指针,并在以后调用实例的方法时使用。例如,假设我有:

class Parent {
protected:
  int a;
public:
  Parent(int v) { a = v; }
  virtual ~Parent() {}
  void setA(int v) { a = v; }
  int getA() { return a; }
};

class Child: public Parent {
protected:
  int b;
public:
  Child(int v1, int v2): Parent(v1) { b = v2; }
  void setA(int v) { b = v; }
  int getB() { return b; }
};

我可能会在以下情况下使用它们:

template <typename C>
void callFn(std::list<Object *> &stack, std::function<void (C*)> fn) {
  Object *value = stack.front();
  stack.pop_front();

  ObjectProxy<C> *proxy = dynamic_cast<ObjectProxy<C> *>(value);
  if (proxy == nullptr) {
   throw std::runtime_error("dynamic cast failed");
  }

  fn(proxy->getInstance());
}

void doSomething(Parent *parent) {
  std::cout << "got: " << parent->getA() << std::endl;
}

int main() {
  std::list<Object *> stack;


  // this works
  stack.push_back(new ObjectProxy<Child>(new Child(1, 2)));
  callFn<Child>(stack, doSomething);

  // this will fail (can't dynamically cast ObjectProxy<Child> to ObjectProxy<Parent>)
  stack.push_back(new ObjectProxy<Child>(new Child(1, 2)));
  callFn<Parent>(stack, doSomething);   
}

如上述评论中所述,此代码因已知原因而失败。在示例代码中,很容易避免调用callFn<Parent>(stack, doSomething)。但是,在我的实际代码中,我使用函数的签名来确定类型,如果它是父类的方法,那么它将自动用于模板参数。

我的问题是,是否有任何方法可以从ObjectProxy类型的对象实现ObjectProxy的动态转换。复杂性的一部分来自于在函数callFn中,您只有父类型而不是子类型。

我研究了通过boost::any使用类型擦除(即ObjectProxy停止被模板化,而是boost::any instance),但在动态转换时仍遇到问题( boost::any_cast是静态的)。我确实在SO上找到dynamic_any的提及,但还没有让它正常工作。

非常感谢您对此问题的任何帮助或见解。

3 个答案:

答案 0 :(得分:2)

动态强制转换失败,因为作为ObjectProxy实例化的类与ObjectProxy参数化中给出的类型不共享相同的层次结构。我看到两种方法可能有所帮助。一,您将赋予ObjectProxy的类型共享一个公共基类,并将动态转换从ObjectProxy移到实例上。

namespace approach2 {
struct object_t {
    virtual ~object_t() { }
};


struct required_base_t {
    virtual ~required_base_t() { }
};

class object_proxy_base_t : public object_t {
    required_base_t* instance_;
public:
    object_proxy_base_t(required_base_t* i) : instance_ (i) { }

    template <class T>
    T* cast_to() const
    {
        return dynamic_cast<T*>(instance_);
    }
};

template <class value_t>
class object_proxy_t : public object_proxy_base_t {
    value_t* instance_;
public:
    object_proxy_t(value_t* i)
    :   object_proxy_base_t (i),
        instance_ (i)
    {
    }
};

template <class value_t>
object_t* new_with_proxy(value_t const& value)
{
    return new object_proxy_t<value_t>(new value_t(value));
}

struct parent_t : required_base_t {
    virtual ~parent_t() { }
};

struct child_t : parent_t {
    virtual ~child_t() { }
};

void f()
{
    object_t* a = new_with_proxy(parent_t()); 
    object_t* b = new_with_proxy(child_t());

    std::cout
        << dynamic_cast<object_proxy_base_t*>(a)->cast_to<parent_t>() << '\n' // works
        << dynamic_cast<object_proxy_base_t*>(b)->cast_to<parent_t>() << '\n' // works
        ;
}

}

如果无法更改ObjectProxy使用的所有类型的基类,则无法使用此方法。这导致第二个解决方案,您使ObjectProxy实例化具有与用于参数化的类型相同的层次结构。

namespace approach3 {
struct object_t {
    virtual ~object_t() { }
};


struct empty_t {
    template <class T>
    empty_t(T*) { }
};

template <class value_t>
class object_proxy_t : public virtual object_t {
    value_t* instance_;
public:
    object_proxy_t(value_t* i) : instance_ (i) { }
};

template <class value_t, class base_t>
class object_proxy_sub_t :
    public object_proxy_t<value_t>,
    public base_t {

public:
    object_proxy_sub_t(value_t* i)
    :   object_proxy_t<value_t>(i),
        base_t (i)
    {
    }
};

template <class base_t, class value_t>
object_t* new_with_proxy(value_t const& value)
{
    return new object_proxy_sub_t<value_t, base_t>(new value_t(value));
}

struct parent_t {
    virtual ~parent_t() { }
};

struct child_t : parent_t {
    virtual ~child_t() { }
};

void f()
{
    object_t* a = new_with_proxy<empty_t>(parent_t()); 
    object_t* b = new_with_proxy<object_proxy_t<parent_t> >(child_t());

    std::cout
        << dynamic_cast<object_proxy_t<parent_t>*>(a) << '\n' // works
        << dynamic_cast<object_proxy_t<parent_t>*>(b) << '\n' // works
        ;
}

}

这种方法对所涉及的类型提出的要求较少,但意味着需要做更多工作来保持层次结构同步。

答案 1 :(得分:1)

基于Bowie Owen的第一个答案,我意识到虽然给出的类型很可能不是来自同一个类(它是一个库),但我可以强迫它发生:

struct ObjectProxyBaseType {
    virtual ~ObjectProxyBaseType() {}
};

template <class T>
class ObjectProxyType: public ObjectProxyBaseType, public T {
public:
  // allow construction via parameters
  template <typename... Args>
  ObjectProxyType(Args &&... args): T(std::move(args)...) {}

  // or construction via copy constructor
  ObjectProxyType(T *t): T(*t) {}

  virtual ~ObjectProxyType() {}
};

因此,如果我有类Child,我可以创建ObjectProxyType<Child>的实例,这会导致它继承ObjectProxyBaseType。其余的代码遵循Bowie的建议:

class ObjectProxy: public Object {
protected:
  ObjectProxyBaseType *instance;
public:
  template <typename T>
  ObjectProxy(ObjectProxyType<T> *i) {
    instance = i;
  }

  template <typename T>
  ObjectProxy(T *value) {
    instance = new ObjectProxyType<T>(value);
  }


  template <typename T>
  T *castTo() const {
    return dynamic_cast<T *>(instance);
  }
};

一个有效的代码示例:

int main() {
    std::list<Object *> stack;
    stack.push_back(new ObjectProxy(new Child(1, 2)));
    callFn<Child>(stack, doSomething);

    stack.push_back(new ObjectProxy(new Child(5, 6)));
    callFn<Parent>(stack, doSomething);
}

答案 2 :(得分:0)

我最近不得不做一些类似的事情。我使用的方法对我有用,但在这种情况下可能不合适;请自行决定。这取决于您(或扩展此代码的人,如果有的话)完全了解将使用哪些层次结构作为模板参数。

所以我们假设这些层次结构如下:

   class Parent1
   class Child1: public Parent1
   class Child11: public Child1
   ...
   class Parent2
   class Child2: public Parent2
   ...

然后你建立一个持有人类。由于一个简单的原因,它有点复杂 - 我的编译器不支持函数上的默认模板参数,因此我使用辅助结构来启用SFINAE。

此类需要能够保存属于所有层次结构的对象(通过基类指针)。

class TypeHolder
{
template<class T, class E=void>
struct GetHelper
{
    static T* Get(const TypeHolder* th) { return nullptr; }
            //you can actually add code here to deal with non-polymorphic types through this class as well, if desirable
};

template<class T>
struct GetHelper<T, typename std::enable_if<std::is_polymorphic<T>::value, void>::type>
{
    static T* Get(const TypeHolder* th)
    {
        switch(th->type)
        {
            case P1: return dynamic_cast<T*>(th->data.p1);
            case P2: return dynamic_cast<T*>(th->data.p2);
                            //and so on...
            default: return nullptr;
        }
    }
};

template<class T, class E=void>
struct SetHelper
{
    static void Set(T*, TypeHolder* th) { th->type = EMPTY; }
};

template<class T>
struct SetHelper<T, typename std::enable_if<std::is_polymorphic<T>::value, void>::type>
{
    static void Set(T* t, TypeHolder* th)
    {
        th->data.p1 = dynamic_cast<Parent1*>(t);
        if(th->data.p1) { th->type = P1; return; }

        th->data.p2 = dynamic_cast<Parent2*>(t);
        if(th->data.p2) { th->type = P2; return; }

                    //...and so on

        th->type = EMPTY;
    }
};

public:
TypeHolder(): type(EMPTY) { }

template<class T>
T* GetInstance() const
{
    return GetHelper<T>::Get(this);
}

template<class T>
void SetInstance(T* t)
{
    SetHelper<T>::Set(t, this);
}

private:
union
{
    Parent1* p1;
    Parent2* p2;
            //...and so on
} data;

enum
{
    EMPTY,
    P1,
    P2
            //...and so on
} type;
};

顺便说一句,我们需要SFINAE技巧的原因是因为dynamic_casts不能在非多态类型上编译。

现在您需要做的只是修改您的类 little 位:)

class ObjectProxyBase
{
public:
    virtual const TypeHolder& GetTypeHolder() const = 0;
};

template<class T>
class ObjectProxy: public Object, public ObjectProxyBase
{
    T* instance;

    static TypeHolder th; //or you can store this somewhere else, or make it a normal (but probably mutable) member
public:
    ObjectProxy(T* t): instance(t) { }

    T* getInstance() const { return instance; }

    const TypeHolder& GetTypeHolder() const { th.SetInstance(instance); return th; }

    //... and the rest of the class
};

template<class T>
TypeHolder ObjectProxy<T>::th;

我希望这段代码实际上是正确的,因为我主要将它输入浏览器窗口(我使用了不同的名称)。

现在是最后一篇:功能。

template <typename C>
void callFn(std::list<Object *> &stack, std::function<void (C*)> fn) {
    Object *value = stack.front();
    stack.pop_front();

    ObjectProxyBase *proxy = dynamic_cast<ObjectProxyBase *>(value);
    if (proxy == nullptr) {
        throw std::runtime_error("dynamic cast failed");
    }

    C* heldobj = proxy->GetTypeHolder().GetInstance<C>(); //I used to have a dynamic_cast here but it was unnecessary
    if (heldobj == nullptr) {
        throw std::runtime_error("object type mismatch");
    }

    fn(heldobj);
}

您只需要对层次结构使用此方法,并且在其他情况下仍然可以将dynamic_cast直接用于ObjectProxy<C>*(实际上,您将要尝试两者并查看是否成功)。

我希望这至少有点帮助。