используя RTTI в C ++ для приведения объекта к правильному типу

Я пытаюсь найти способ динамического приведения экземпляра дочернего класса к его родителю в несколько сложных условиях.

В частности, у меня есть объектная иерархия, которая выглядит примерно так (я сильно упростила, поэтому, если что-то не имеет смысла, это может быть связано с упрощением):

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 статично). Я нашел упоминание dynamic_any на ТАК, но еще не заставили его работать должным образом.

Любая помощь или понимание проблемы очень ценится.

3

Решение

Динамическое приведение не выполняется, потому что классы, которые являются экземплярами 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
;
}

}

Этот подход предъявляет меньше требований к задействованным типам, но требует больше работы для синхронизации иерархий.

2

Другие решения

Основываясь на первом ответе Боуи Оуэна, я понял, что хотя указанные типы, вероятно, не будут производными от одного и того же класса (это библиотека), я мог бы заставить это произойти:

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, Остальная часть кода следует предложению Боуи:

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

Мне пришлось сделать что-то похожее в последнее время. Я использовал подход, который работал для меня, но не мог бы быть уместным в этом случае; используйте свое усмотрение. Это зависит от того факта, что вы (или лицо, расширяющее этот код, если таковой имеется) полностью осведомлены о том, какие иерархии будут использоваться в качестве параметров шаблона.

Итак, скажем, эти иерархии следующие:

класс Parent1
класс Child1: public 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, заключается в динамических трансляциях, которые не будут компилироваться на неполиморфных типах.

Теперь все, что вам нужно сделать, это изменить ваши классы просто немного немного 🙂

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>*[/ NOEDIT] в других случаях (по сути, вы захотите попробовать оба и посмотреть, если это удастся).

Я надеюсь, что это будет хоть немного полезно.

0
По вопросам рекламы [email protected]