c++ rtti

c++ - Creando un nuevo objeto a partir de información de tipo dinámico



rtti (8)

En C ++, ¿hay alguna forma de consultar el tipo de un objeto y luego usar esa información para crear dinámicamente un nuevo objeto del mismo tipo?

Por ejemplo, digamos que tengo una jerarquía simple de 3 clases:

class Base class Foo : public Base class Bar : public Base

Ahora supongamos que te entrego un objeto moldeado como tipo Base, que en realidad es del tipo Foo. ¿Hay alguna forma de consultar el tipo y usar esa información para crear posteriormente nuevos objetos del tipo Foo?


En C ++, hay alguna forma de consultar el tipo de un objeto ...

Sí, usar el operador typeid()

Por ejemplo:

// typeid, polymorphic class #include <iostream> #include <typeinfo> #include <exception> using namespace std; class CBase { virtual void f(){} }; class CDerived : public CBase {}; int main () { try { CBase* a = new CBase; CBase* b = new CDerived; cout << "a is: " << typeid(a).name() << ''/n''; cout << "b is: " << typeid(b).name() << ''/n''; cout << "*a is: " << typeid(*a).name() << ''/n''; cout << "*b is: " << typeid(*b).name() << ''/n''; } catch (exception& e) { cout << "Exception: " << e.what() << endl; } return 0; }

Salida :

a is: class CBase * b is: class CBase * *a is: class CBase *b is: class CDerived

Si el tipo typeid evalates es un puntero precedido por el operador de desreferencia (*), y este puntero tiene un valor nulo, typeid lanza una excepción bad_typeid

Leer more.....


Metodo de clon

El lenguaje que consulta el tipo no proporciona nada y le permite construir a partir de esa información, pero puede proporcionar la capacidad para la jerarquía de su clase de varias maneras, la más sencilla de las cuales es utilizar un método virtual:

struct Base { virtual ~Base(); virtual std::auto_ptr<Base> clone(/*desired parameters, if any*/) const = 0; };

Esto hace algo ligeramente diferente: clonar el objeto actual. Con frecuencia, esto es lo que desea y le permite mantener los objetos como plantillas, que luego puede clonar y modificar según lo desee.

Ampliando Tronic , incluso puede generate la función de clonar .

¿Por qué auto_ptr ? Por lo tanto, puede usar nuevo para asignar el objeto, hacer explícita la transferencia de propiedad, y la persona que llama no tiene dudas de que eliminar debe desasignarlo. Por ejemplo:

Base& obj = *ptr_to_some_derived; { // since you can get a raw pointer, you have not committed to anything // except that you might have to type ".release()" Base* must_free_me = obj.clone().release(); delete must_free_me; } { // smart pointer types can automatically work with auto_ptr // (of course not all do, you can still use release() for them) boost::shared_ptr<Base> p1 (obj.clone()); auto_ptr<Base> p2 (obj.clone()); other_smart_ptr<Base> p3 (obj.clone().release()); } { // automatically clean up temporary clones // not needed often, but impossible without returning a smart pointer obj.clone()->do_something(); }

Fábrica de objetos

Si prefiere hacer exactamente lo que pidió y obtener una fábrica que pueda usarse independientemente de las instancias:

struct Factory {}; // give this type an ability to make your objects struct Base { virtual ~Base(); virtual Factory get_factory() const = 0; // implement in each derived class // to return a factory that can make the derived class // you may want to use a return type of std::auto_ptr<Factory> too, and // then use Factory as a base class };

Gran parte de la misma lógica y funcionalidad se pueden usar para un método de clonación, ya que get_factory cumple la mitad del mismo rol, y la única diferencia es el tipo de retorno (y su significado).

También he cubierto fábricas un couple times ya. Podría adaptar mi clase SimpleFactory para que su objeto de fábrica (devuelto por get_factory ) tuviera una referencia a una fábrica global más los parámetros para pasar a crear (por ejemplo, el nombre registrado de la clase; considere cómo aplicar boost :: function y boost :: bind to hacer esto fácil de usar).


Cuando hay muchas clases derivadas de la misma clase base, este código le ahorrará tener que incluir métodos de clonación en cada clase. Es una forma de clonación más conveniente que involucra plantillas y una subclase intermedia. Es factible si la jerarquía es lo suficientemente superficial.

struct PureBase { virtual Base* Clone() { return nullptr; }; }; template<typename T> struct Base : PureBase { virtual Base* Clone() { return new T(); } }; struct Derived : Base<Derived> {}; int main() { PureBase* a = new Derived(); PureBase* b = a->Clone(); // typeid(*b) == typeid(Derived) }


La forma comúnmente utilizada para crear copias de objetos por clase base es agregar un método de clonación, que es esencialmente un constructor de copias polimórficas. Esta función virtual normalmente debe definirse en todas las clases derivadas, pero puede evitar copiar y pegar utilizando el Patrón de plantilla curioso recurrente :

// Base class has a pure virtual function for cloning class Shape { public: virtual ~Shape() {} // Polymorphic destructor to allow deletion via Shape* virtual Shape* clone() const = 0; // Polymorphic copy constructor }; // This CRTP class implements clone() for Derived template <typename Derived> class Shape_CRTP: public Shape { public: Shape* clone() const { return new Derived(dynamic_cast<Derived const&>(*this)); } }; // Every derived class inherits from Shape_CRTP instead of Shape // Note: clone() needs not to be defined in each class Square: public Shape_CRTP<Square> {}; class Circle: public Shape_CRTP<Circle> {}; // Now you can clone shapes: int main() { Shape* s = new Square(); Shape* s2 = s->clone(); delete s2; delete s; }

Tenga en cuenta que puede usar la misma clase CRTP para cualquier funcionalidad que sería la misma en todas las clases derivadas pero que requiere conocimiento del tipo derivado. Hay muchos otros usos para esto además de clone (), por ejemplo, doble despacho.


Puede usar, por ejemplo, typeid para consultar el tipo dinámico de un objeto, pero no conozco una manera de instanciar directamente un nuevo objeto a partir de la información de tipo.

Sin embargo, aparte del enfoque de clone mencionado anteriormente, podría usar una fábrica:

#include <typeinfo> #include <iostream> class Base { public: virtual void foo() const { std::cout << "Base object instantiated." << std::endl; } }; class Derived : public Base { public: virtual void foo() const { std::cout << "Derived object instantiated." << std::endl; } }; class Factory { public: static Base* createFrom( const Base* x ) { if ( typeid(*x) == typeid(Base) ) { return new Base; } else if ( typeid(*x) == typeid(Derived) ) { return new Derived; } else { return 0; } } }; int main( int argc, char* argv[] ) { Base* X = new Derived; if ( X != 0 ) { std::cout << "X says: " << std::endl; X->foo(); } Base* Y = Factory::createFrom( X ); if ( Y != 0 ) { std::cout << "Y says: " << std::endl; Y->foo(); } return 0; }

PS : La parte esencial de este ejemplo de código es, por supuesto, el método Factory::createFrom . (Probablemente no sea el código C ++ más hermoso, ya que mi C ++ se ha oxidado un poco. El método de fábrica probablemente no debería ser estático, pensándolo bien).


Solo hay algunas maneras de hacer esto.

El primero y en mi humilde opinión el más feo es:

Base * newObjectOfSameType( Base * b ) { if( dynamic_cast<Foo*>( b ) ) return new Foo; if( dynamic_cast<Bar*>( b ) ) return new Bar; }

Tenga en cuenta que esto solo funcionará si tiene habilitado RTTI y la Base contiene alguna función virtual.

La segunda versión más clara es agregar una función de clonación virtual pura a la clase base

struct Base { virtual Base* clone() const=0; } struct Foo : public Base { Foo* clone() const { return new Foo(*this); } struct Bar : public Base { Bar* clone() const { return new Bar(*this); } Base * newObjectOfSameType( Base * b ) { return b->clone(); }

Esto es mucho más ordenado.

Una cosa interesante / interesante de esto es que Foo::clone devuelve un Foo* , mientras que Bar::clone devuelve un Bar* . Podría esperar que esto rompa cosas, pero todo funciona debido a una característica de C ++ llamada tipos de retorno covariantes.

Desafortunadamente, los tipos de retorno covariantes no funcionan para los punteros inteligentes, por lo que al usar sharted_ptrs su código se vería así.

struct Base { virtual shared_ptr<Base> clone() const=0; } struct Foo : public Base { shared_ptr<Base> clone() const { return shared_ptr<Base>(new Foo(*this) ); } struct Bar : public Base { shared_ptr<Base> clone() const { return shared_ptr<Base>(new Bar(*this)); } shared_ptr<Base> newObjectOfSameType( shared_ptr<Base> b ) { return b->clone(); }


Utilicé macros en mi proyecto para sintetizar tales métodos. Solo estoy investigando este enfoque ahora, por lo que puedo estar equivocado, pero aquí hay una respuesta a su pregunta en mi código de IAllocable.hh. Tenga en cuenta que uso GCC 4.8, pero espero 4.7 trajes.

#define SYNTHESIZE_I_ALLOCABLE / public: / auto alloc() -> __typeof__(this) { return new (__typeof__(*this))(); } / IAllocable * __IAllocable_alloc() { return new (__typeof__(*this))(); } / private: class IAllocable { public: IAllocable * alloc() { return __IAllocable_alloc(); } protected: virtual IAllocable * __IAllocable_alloc() = 0; };

Uso:

class Usage : public virtual IAllocable { SYNTHESIZE_I_ALLOCABLE public: void print() { printf("Hello, world!/n"); } }; int main() { { Usage *a = new Usage; Usage *b = a->alloc(); b->print(); delete a; delete b; } { IAllocable *a = new Usage; Usage *b = dynamic_cast<Usage *>(a->alloc()); b->print(); delete a; delete b; } }

Espero eso ayude.


class Base { public: virtual ~Base() { } }; class Foo : public Base { }; class Bar : public Base { }; template<typename T1, typename T2> T1* fun(T1* obj) { T2* temp = new T2(); return temp; } int main() { Base* b = new Foo(); fun<Base,Foo>(b); }