Я пытаюсь найти способ динамического приведения экземпляра дочернего класса к его родителю в несколько сложных условиях.
В частности, у меня есть объектная иерархия, которая выглядит примерно так (я сильно упростила, поэтому, если что-то не имеет смысла, это может быть связано с упрощением):
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
на ТАК, но еще не заставили его работать должным образом.
Любая помощь или понимание проблемы очень ценится.
Динамическое приведение не выполняется, потому что классы, которые являются экземплярами 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
;
}
}
Этот подход предъявляет меньше требований к задействованным типам, но требует больше работы для синхронизации иерархий.
Основываясь на первом ответе Боуи Оуэна, я понял, что хотя указанные типы, вероятно, не будут производными от одного и того же класса (это библиотека), я мог бы заставить это произойти:
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);
}
Мне пришлось сделать что-то похожее в последнее время. Я использовал подход, который работал для меня, но не мог бы быть уместным в этом случае; используйте свое усмотрение. Это зависит от того факта, что вы (или лицо, расширяющее этот код, если таковой имеется) полностью осведомлены о том, какие иерархии будут использоваться в качестве параметров шаблона.
Итак, скажем, эти иерархии следующие:
класс 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
[/ NOEDIT] в других случаях (по сути, вы захотите попробовать оба и посмотреть, если это удастся).<C>
*
Я надеюсь, что это будет хоть немного полезно.