Question

I'm trying to figure out a way to dynamically cast an instance of a child class to its parent in a somewhat difficult set of conditions.

Specifically, I have a an object hierarchy that looks something like (I've simplified a lot, so if something doesn't make sense, it might be due to the simplification):

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

The ObjectProxy class essentially acts as a wrapper to allow other types to be used in the Object hierarchy. Specifically, it allows pointers to class instances to be kept, and used later when invoking the instance's methods. For example, suppose I have:

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

I might use them in the following situation:

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

As noted in the above comments, this code fails for a known reason. In the sample code, it's easy to avoid invoking callFn<Parent>(stack, doSomething). However, in my real code, I am using the signature of the function to determine type, and if its a method for the parent class, that will automatically be used for the template parameter.

My question is if there is any way to achieve the dynamic cast from ObjectProxy from an object of type of ObjectProxy. Part of the complication comes from the fact that in the function callFn, you only have the Parent type and not the child type.

I looked into using type-erasure via boost::any (i.e. ObjectProxy stops being templated, and instead has boost::any instance), but still ran into problems when it came to dynamic-casting (boost::any_cast is static). I did find mention to a dynamic_any on SO, but have not gotten it to work properly yet.

Any help or insight into the problem is greatly appreciated.

Était-ce utile?

La solution

The dynamic cast is failing because the classes that are instantiations of ObjectProxy do not share the same hierarchy as the types given in the parameterisation of ObjectProxy. I see two approaches that may help. One, you make the types given to ObjectProxy share a single common base class and move the dynamic cast away from ObjectProxy and onto the instances.

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

}

This approach is not possible if you cannot change the base classes of all types used by ObjectProxy. Which leads to the second solution where you make ObjectProxy instantiations have the same hierarchy as the types used to parameterise it.

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

}

This approach places fewer requirements on the types involved but means more work to keep the hierarchies in sync.

Autres conseils

Building off of Bowie Owen's first answer, I realized that while the types given would likely not be derived from the same class (it's a library), I could force that to occur:

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() {}
};

Thus, if I have class Child, I can create an instance of ObjectProxyType<Child>, which causes it to also inherit ObjectProxyBaseType. The rest of the code follows Bowie's suggestion:

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

And an example of code that works:

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

I've had to do something somewhat similar recently. I've used an approach which worked for me, but might not be appropriate in this case; use your discretion. This hinges on the fact that you (or the person extending this code, if any) have full knowledge of what hierarchies will be used as template parameters.

So let's say these hierarchies are the following:

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

Then you build a holder class. It is a bit complicated for a simple reason - my compiler doesn't support default template parameters on functions, so I am using helper structs to enable SFINAE.

This class needs to be able to hold objects belonging to all hierarchies (through a base class pointer).

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

By the way, the reason we need the SFINAE trick is because of the dynamic_casts, which will not compile on non-polymorphic types.

Now all you need to do is modify your classes just a little bit :)

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;

I hope this code is actually correct, since I mostly typed it into the browser window (mine used different names).

And now for the final piece: the function.

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

You only need to use this approach for hierarchies, and can still use the dynamic_cast directly to ObjectProxy<C>* in other cases (essentially, you'll want to try both and see if one succeeds).

I hope this is at least a little bit helpful.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top