3

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

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

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 类型的对象中实现动态转换。部分复杂性来自这样一个事实,即在 functioncallFn中,您只有 Parent 类型,而没有 child 类型。

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

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

4

3 回答 3

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

}

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

于 2012-12-07T01:18:34.213 回答
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);
}
于 2012-12-07T16:31:19.407 回答
0

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

因此,假设这些层次结构如下:

   类 Parent1
   类 Child1:公共 Parent1
   类 Child11:公共 Child1
   ...
   类 Parent2
   Child2 类:公共 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,它不会在非多态类型上编译。

现在你需要做的就是稍微修改你的类:)

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直接使用 to (基本上,您需要同时尝试两种方法,看看是否成功)。ObjectProxy<C>*

我希望这至少有点帮助。

于 2012-12-07T02:16:21.493 回答