una resueltos resolucion relaciones programacion orientada operador objetos miembros entre ejercicios ejemplos codigo clases clase ambito c++ inheritance

resueltos - operador de resolucion de ambito c++



¿Es posible cambiar la clase de un objeto C++ después de la instanciación? (15)

Estoy dispuesto a destruir el objeto antiguo y crear uno nuevo, siempre que pueda crear el nuevo objeto en la misma dirección de memoria, para que los punteros existentes no se rompan.

El estándar de C ++ aborda explícitamente esta idea en la sección 3.8 (Vida útil del objeto):

Si , una vez finalizado el tiempo de vida de un objeto y antes de que se reutilice o libere el almacenamiento que ocupa el objeto, se crea un nuevo objeto en la ubicación de almacenamiento ocupada por el objeto original, un puntero que apunta al objeto original , una referencia que referido al objeto original, o el nombre del objeto original se referirá automáticamente al nuevo objeto y, una vez que se haya iniciado el tiempo de vida del nuevo objeto, se puede usar para manipular el nuevo objeto <snip>

Oh wow, esto es exactamente lo que querías. Pero no mostré toda la regla. Aquí está el resto:

si :

  • el almacenamiento para el nuevo objeto se superpone exactamente a la ubicación de almacenamiento que ocupó el objeto original, y
  • el nuevo objeto es del mismo tipo que el objeto original (ignorando los cv-qualifiers de nivel superior) , y
  • el tipo del objeto original no es const-calificado, y, si es un tipo de clase, no contiene ningún miembro de datos no estático cuyo tipo es const-calificado o un tipo de referencia, y
  • el objeto original era el objeto más derivado (1.8) de tipo T y el nuevo objeto es el objeto más derivado de tipo T (es decir, no son subobjetos de clase base).

Así que su idea ha sido pensada por el comité de lenguaje y específicamente hecha ilegal, incluida la solución furtiva que "Tengo un subobjeto de clase base del tipo correcto, voy a hacer un nuevo objeto en su lugar", que el último punto se detiene en sus pistas.

Puede reemplazar un objeto con un objeto de un tipo diferente como muestra la respuesta de @ RossRidge. O puede reemplazar un objeto y seguir usando punteros que existían antes del reemplazo. Pero no puedes hacer ambas cosas juntas.

Sin embargo, al igual que la cita famosa: "Cualquier problema en la informática puede resolverse agregando una capa de indirección" y eso también es cierto aquí.

En lugar de su método sugerido

Derived d; Base* p = &d; new (p) Base(); // makes p invalid! Plus problems when d''s destructor is automatically called

Tu puedes hacer:

unique_ptr<Base> p = make_unique<Derived>(); p.reset(make_unique<Base>());

Si oculta este puntero y la mano leve dentro de otra clase, tendrá el "patrón de diseño" como estado o estrategia mencionados en otras respuestas. Pero todos dependen de un nivel adicional de indirección.

Tengo un montón de clases que heredan los mismos atributos de una clase base común. La clase base implementa algunas funciones virtuales que funcionan en casos generales, mientras que cada subclase implementa de nuevo esas funciones virtuales para una variedad de casos especiales.

Aquí está la situación: quiero que la especialidad de estos objetos sub-clasificados sea prescindible. Esencialmente, me gustaría implementar una función expend() que hace que un objeto pierda su identidad de subclase y vuelva a ser una instancia de clase base con los comportamientos de caso general implementados en la clase base.

Debo señalar que las clases derivadas no introducen ninguna variable adicional, por lo que tanto la clase base como las derivadas deben tener el mismo tamaño en la memoria.

Estoy dispuesto a destruir el objeto antiguo y crear uno nuevo, siempre que pueda crear el nuevo objeto en la misma dirección de memoria, para que los punteros existentes no se rompan.

El siguiente intento no funciona y produce un comportamiento aparentemente inesperado. ¿Que me estoy perdiendo aqui?

#include <iostream> class Base { public: virtual void whoami() { std::cout << "I am Base/n"; } }; class Derived : public Base { public: void whoami() { std::cout << "I am Derived/n"; } }; Base* object; int main() { object = new Derived; //assign a new Derived class instance object->whoami(); //this prints "I am Derived" Base baseObject; *object = baseObject; //reassign existing object to a different type object->whoami(); //but it *STILL* prints "I am Derived" (!) return 0; }


Además de otras respuestas, puede usar punteros a funciones (o cualquier envoltorio sobre ellos, como std::function ) para lograr el bevahior necesario:

void print_base(void) { cout << "This is base" << endl; } void print_derived(void) { cout << "This is derived" << endl; } class Base { public: void (*print)(void); Base() { print = print_base; } }; class Derived : public Base { public: Derived() { print = print_derived; } }; int main() { Base* b = new Derived(); b->print(); // prints "This is derived" *b = Base(); b->print(); // prints "This is base" return 0; }

Además, dicho enfoque de punteros de función le permitiría cambiar cualquiera de las funciones de los objetos en tiempo de ejecución, sin limitarlo a algunos conjuntos de miembros ya definidos implementados en clases derivadas.


Consideraría regularizar tu tipo.

class Base { public: virtual void whoami() { std::cout << "Base/n"; } std::unique_ptr<Base> clone() const { return std::make_unique<Base>(*this); } virtual ~Base() {} }; class Derived: public Base { virtual void whoami() overload { std::cout << "Derived/n"; }; std::unique_ptr<Base> clone() const override { return std::make_unique<Derived>(*this); } public: ~Derived() {} }; struct Base_Value { private: std::unique_ptr<Base> pImpl; public: void whoami () { pImpl->whoami(); } template<class T, class...Args> void emplace( Args&&...args ) { pImpl = std::make_unique<T>(std::forward<Args>(args)...); } Base_Value()=default; Base_Value(Base_Value&&)=default; Base_Value& operator=(Base_Value&&)=default; Base_Value(Base_Value const&o) { if (o.pImpl) pImpl = o.pImpl->clone(); } Base_Value& operator=(Base_Value&& o) { auto tmp = std::move(o); swap( pImpl, tmp.pImpl ); return *this; } };

Ahora, un Base_Value es semánticamente un tipo de valor que se comporta polimórficamente.

Base_Value object; object.emplace<Derived>(); object.whoami(); object.emplace<Base>(); object.whoami();

Podría ajustar una instancia Base_Value en un puntero inteligente, pero no me molestaría.


Hay un error simple en tu programa. Usted asigna los objetos, pero no los punteros:

int main() { Base* object = new Derived; //assign a new Derived class instance object->whoami(); //this prints "I am Derived" Base baseObject;

Ahora asigna *object baseObject a *object que sobrescribe el objeto Derived con un objeto Base . Sin embargo, esto funciona bien porque está sobrescribiendo un objeto de tipo Derived con un objeto de tipo Base . El operador de asignación predeterminado simplemente asigna todos los miembros, que en este caso no hace nada. El objeto no puede cambiar su tipo y sigue siendo un Objetos Derived posteriormente. En general, esto puede conducir a problemas graves, por ejemplo, corte de objetos.

*object = baseObject; //reassign existing object to a different type object->whoami(); //but it *STILL* prints "I am Derived" (!) return 0; }

Si, en cambio, solo asigna el puntero, funcionará como se esperaba, pero solo tiene dos objetos, uno de tipo Derived y otro de Base , pero creo que desea un comportamiento más dinámico. Parece que podría implementar la especialidad como Decorator .

Tiene una clase base con alguna operación, y varias clases derivadas que cambian / modifican / extienden el comportamiento de clase base de esa operación. Como se basa en la composición, puede cambiarse dinámicamente. El truco es almacenar una referencia de clase base en las instancias de Decorator y usarla para todas las demás funcionalidades.

class Base { public: virtual void whoami() { std::cout << "I am Base/n"; } virtual void otherFunctionality() {} }; class Derived1 : public Base { public: Derived1(Base* base): m_base(base) {} virtual void whoami() override { std::cout << "I am Derived/n"; // maybe even call the base-class implementation // if you just want to add something } virtual void otherFunctionality() { base->otherFunctionality(); } private: Base* m_base; }; Base* object; int main() { Base baseObject; object = new Derived(&baseObject); //assign a new Derived class instance object->whoami(); //this prints "I am Derived" // undecorate delete object; object = &baseObject; object->whoami(); return 0; }

Existen patrones alternativos como Estrategia que implementan diferentes casos de uso resp. resolver diferentes problemas Probablemente sea bueno leer la documentación del patrón con un enfoque especial en las secciones de Intento y Motivación.


No estoy en desacuerdo con el consejo de que este no es un gran diseño, pero otra manera segura de hacerlo es con un sindicato que puede albergar cualquiera de las clases que desea cambiar, ya que el estándar garantiza que puede contener cualquier de ellos. Aquí hay una versión que encapsula todos los detalles dentro de la unión misma:

#include <cassert> #include <cstdlib> #include <iostream> #include <new> #include <typeinfo> class Base { public: virtual void whoami() { std::cout << "I am Base/n"; } virtual ~Base() {} // Every base class with child classes that might be deleted through a pointer to the // base must have a virtual destructor! }; class Derived : public Base { public: void whoami() { std::cout << "I am Derived/n"; } // At most one member of any union may have a default member initializer in C++11, so: Derived(bool) : Base() {} }; union BorD { Base b; Derived d; // Initialize one member. BorD(void) : b() {} // These defaults are not used here. BorD( const BorD& ) : b() {} // No per-instance data to worry about! // Otherwise, this could get complicated. BorD& operator= (const BorD& x) // Boilerplate: { if ( this != &x ) { this->~BorD(); new(this) BorD(x); } return *this; } BorD( const Derived& x ) : d(x) {} // The constructor we use. // To destroy, be sure to call the base class’ virtual destructor, // which works so long as every member derives from Base. ~BorD(void) { dynamic_cast<Base*>(&this->b)->~Base(); } Base& toBase(void) { // Sets the active member to b. Base* const p = dynamic_cast<Base*>(&b); assert(p); // The dynamic_cast cannot currently fail, but check anyway. if ( typeid(*p) != typeid(Base) ) { p->~Base(); // Call the virtual destructor. new(&b) Base; // Call the constructor. } return b; } }; int main(void) { BorD u(Derived{false}); Base& reference = u.d; // By the standard, u, u.b and u.d have the same address. reference.whoami(); // Should say derived. u.toBase(); reference.whoami(); // Should say base. return EXIT_SUCCESS; }

Una forma más sencilla de obtener lo que desea es, probablemente, mantener un contenedor de Base * y reemplazar los elementos individualmente según sea necesario con new y delete . (¡Todavía recuerde declarar su destructor virtual ! Eso es importante con las clases polimórficas, por lo que llama al destructor correcto para esa instancia, no al destructor de la clase base.) Esto podría ahorrarle algunos bytes adicionales en las instancias de las clases más pequeñas. Sin embargo, tendría que jugar con punteros inteligentes para obtener una eliminación automática segura. Una ventaja de las uniones sobre los indicadores inteligentes para la memoria dinámica es que no tiene que asignar ni liberar más objetos en el montón, pero puede simplemente reutilizar la memoria que tiene.


No, no es posible cambiar el tipo de objeto una vez instanciado.

*object = baseObject; no cambia el tipo de object , simplemente llama a un operador de asignación generado por el compilador.

Hubiera sido una cuestión diferente si hubieras escrito

object = new Base;

(recuerda llamar a delete forma natural, actualmente tu código pierde un objeto).

C ++ 11 en adelante le brinda la capacidad de mover los recursos de un objeto a otro; ver

http://en.cppreference.com/w/cpp/utility/move


Puede a costa de romper las buenas prácticas y mantener un código inseguro. Otras respuestas te proporcionarán trucos desagradables para lograr esto.

No me gustan las respuestas que simplemente dicen "no deberías hacer eso", pero me gustaría sugerir que probablemente haya una mejor manera de lograr el resultado que buscas.

El patrón de estrategia como se sugiere en un comentario de @ manni66 es bueno.

También debe pensar en el diseño orientado a datos , ya que una jerarquía de clases no parece una elección inteligente en su caso.


Puede hacer lo que está pidiendo literalmente con la colocación nueva y una llamada al destructor explícita. Algo como esto:

#include <iostream> #include <stdlib.h> class Base { public: virtual void whoami() { std::cout << "I am Base/n"; } }; class Derived : public Base { public: void whoami() { std::cout << "I am Derived/n"; } }; union Both { Base base; Derived derived; }; Base *object; int main() { Both *tmp = (Both *) malloc(sizeof(Both)); object = new(&tmp->base) Base; object->whoami(); Base baseObject; tmp = (Both *) object; tmp->base.Base::~Base(); new(&tmp->derived) Derived; object->whoami(); return 0; }

Sin embargo, como dijo matb, esto realmente no es un buen diseño. Yo recomendaría reconsiderar lo que estás tratando de hacer. Algunas de las otras respuestas aquí también podrían resolver su problema, pero creo que cualquier cosa con respecto a la idea de lo que está pidiendo va a ser kludge. Debe considerar seriamente diseñar su aplicación para poder cambiar el puntero cuando cambie el tipo de objeto.


Puede introducir una variable en la clase base, de modo que la huella de memoria permanezca igual. Al establecer el indicador, fuerza a llamar a la implementación derivada o de la clase base.

#include <iostream> class Base { public: Base() : m_useDerived(true) { } void setUseDerived(bool value) { m_useDerived = value; } void whoami() { m_useDerived ? whoamiImpl() : Base::whoamiImpl(); } protected: virtual void whoamiImpl() { std::cout << "I am Base/n"; } private: bool m_useDerived; }; class Derived : public Base { protected: void whoamiImpl() { std::cout << "I am Derived/n"; } }; Base* object; int main() { object = new Derived; //assign a new Derived class instance object->whoami(); //this prints "I am Derived" object->setUseDerived(false); object->whoami(); //should print "I am Base" return 0; }


Si y no. Una clase de C ++ define el tipo de región de memoria que es un objeto. Una vez que la región de memoria ha sido instanciada, su tipo está configurado. Puede intentar trabajar con el sistema de tipo seguro, pero el compilador no le permitirá salirse con la suya. Tarde o temprano te disparará en el pie, porque el compilador hizo una suposición sobre los tipos que violaste, y no hay forma de evitar que el compilador haga tal suposición de una manera portátil.

Sin embargo, hay un patrón de diseño para esto: es "Estado". Extrae lo que cambia en su propia jerarquía de clases, con su propia clase base, y tiene sus objetos almacenan un puntero a la base de estado abstracto de esta nueva jerarquía. A continuación, puede cambiarlos al contenido de su corazón.


Su tarea solo asigna variables miembro, no el puntero utilizado para las llamadas a función de miembro virtual. Puede reemplazarlo fácilmente con copia de memoria completa:

//*object = baseObject; //this assignment was wrong memcpy(object, &baseObject, sizeof(baseObject));

Tenga en cuenta que, al igual que su intento de asignación, esto reemplazaría las variables miembro en *object por las del recién construido baseObject - probablemente no sea lo que realmente desea, así que primero tendrá que copiar las variables miembro originales al nuevo baseObject, usando cualquiera operador de asignación o constructor de copia antes de memcpy , es decir

Base baseObject = *object;

Es posible copiar solo el puntero de la tabla de funciones virtuales, pero eso dependería del conocimiento interno sobre cómo el compilador lo almacena, por lo que no es recomendable.

Si mantener el objeto en la misma dirección de memoria no es crucial, un enfoque más simple y mejor sería lo contrario: construir un nuevo objeto base y copiar las variables miembro del objeto original, es decir, usar un constructor de copia.

object = new Base(*object);

Pero también tendrá que eliminar el objeto original, por lo que el delineador de líneas anterior no será suficiente: debe recordar el puntero original en otra variable para eliminarlo, etc. Si tiene múltiples referencias a ese original necesitará actualizarlos todos, y a veces esto puede ser bastante complicado. Entonces la forma memcpy es mejor.

Si algunas de las variables miembro son punteros a objetos que se crean / eliminan en el constructor / destructor del objeto principal, o si tienen un operador de asignación más especializado u otra lógica personalizada, tendrá más trabajo en sus manos, pero para variables de miembros triviales esto debería ser lo suficientemente bueno.


Sugiero que uses el Patrón de estrategia, por ejemplo

#include <iostream> class IAnnouncer { public: virtual ~IAnnouncer() { } virtual void whoami() = 0; }; class AnnouncerA : public IAnnouncer { public: void whoami() override { std::cout << "I am A/n"; } }; class AnnouncerB : public IAnnouncer { public: void whoami() override { std::cout << "I am B/n"; } }; class Foo { public: Foo(IAnnouncer *announcer) : announcer(announcer) { } void run() { // Do stuff if(nullptr != announcer) { announcer->whoami(); } // Do other stuff } void expend(IAnnouncer* announcer) { this->announcer = announcer; } private: IAnnouncer *announcer; }; int main() { AnnouncerA a; Foo foo(&a); foo.run(); // Ready to "expend" AnnouncerB b; foo.expend(&b); foo.run(); return 0; }

Este es un patrón muy flexible que tiene al menos algunos beneficios sobre intentar tratar el problema a través de la herencia:

  • Puede cambiar fácilmente el comportamiento de Foo más tarde implementando un nuevo Anunciador
  • Sus Anunciadores (y sus Foos) son fácilmente contrastados
  • Puedes reutilizar tus Anunciadores en cualquier otro lugar del código

Sugiero que echen un vistazo al viejo debate "Composición vs. Herencia" (véase https://www.thoughtworks.com/insights/blog/composition-vs-inheritance-how-choose )

PD. ¡Has filtrado un Derived en tu publicación original! Eche un vistazo a std :: unique_ptr si está disponible.


Tengo 2 soluciones. Uno más simple que no conserva la dirección de memoria, y uno que conserva la dirección de memoria.

Ambos requieren que proporcione downcasts desde Base a Derivados, lo cual no es un problema en su caso.

struct Base { int a; Base(int a) : a{a} {}; virtual ~Base() = default; virtual auto foo() -> void { cout << "Base " << a << endl; } }; struct D1 : Base { using Base::Base; D1(Base b) : Base{b.a} {}; auto foo() -> void override { cout << "D1 " << a << endl; } }; struct D2 : Base { using Base::Base; D2(Base b) : Base{b.a} {}; auto foo() -> void override { cout << "D2 " << a << endl; } };

Para el anterior, puede crear un puntero inteligente que aparentemente pueda cambiar los datos retenidos entre las clases Derivadas (y las básicas):

template <class B> struct Morpher { std::unique_ptr<B> obj; template <class D> auto morph() { obj = std::make_unique<D>(*obj); } auto operator->() -> B* { return obj.get(); } }; int main() { Morpher<Base> m{std::make_unique<D1>(24)}; m->foo(); // D1 24 m.morph<D2>(); m->foo(); // D2 24 }

La magia está en

m.morph<D2>();

que cambia el objeto retenido que preserva los miembros de datos (en realidad usa el ctor de conversión).

Si necesita conservar la ubicación de la memoria , puede adaptar lo anterior para usar un búfer y una ubicación nueva en lugar de unique_ptr . Es un poco más de trabajo prestar mucha más atención, pero le da exactamente lo que necesita:

template <class B> struct Morpher { std::aligned_storage_t<sizeof(B)> buffer_; B *obj_; template <class D> Morpher(const D &new_obj) : obj_{new (&buffer_) D{new_obj}} { static_assert(std::is_base_of<B, D>::value && sizeof(D) == sizeof(B) && alignof(D) == alignof(B)); } Morpher(const Morpher &) = delete; auto operator=(const Morpher &) = delete; ~Morpher() { obj_->~B(); } template <class D> auto morph() { static_assert(std::is_base_of<B, D>::value && sizeof(D) == sizeof(B) && alignof(D) == alignof(B)); obj_->~B(); obj_ = new (&buffer_) D{*obj_}; } auto operator-> () -> B * { return obj_; } }; int main() { Morpher<Base> m{D1{24}}; m->foo(); // D1 24 m.morph<D2>(); m->foo(); // D2 24 m.morph<Base>(); m->foo(); // Base 24 }

Esto es, por supuesto, el hueso desnudo absoluto. Puede agregar move ctor, operador de referencia, etc.


no puede cambiar al tipo de objeto después de la creación de instancias, como puede ver en su ejemplo, tiene un puntero a una clase Base (de tipo clase base) por lo que este tipo está pegado a él hasta el final.

  • el puntero de base puede apuntar a objeto superior o inferior, no significa que haya cambiado su tipo:

    Base* ptrBase; // pointer to base class (type) ptrBase = new Derived; // pointer of type base class `points to an object of derived class` Base theBase; ptrBase = &theBase; // not *ptrBase = theDerived: Base of type Base class points to base Object.

  • los punteros son mucho más fuertes, flexibles, potentes y peligrosos, por lo que debe manejarlos con precaución.

en tu ejemplo, puedo escribir:

Base* object; // pointer to base class just declared to point to garbage Base bObject; // object of class Base *object = bObject; // as you did in your code

encima, es un desastre que asigna valor al puntero no asignado. el programa se bloqueará.

en su ejemplo, escapó del bloqueo a través de la memoria que se asignó al principio:

object = new Derived;

nunca es buena idea asignar un value and not address de un objeto de una subclase a la clase base. Sin embargo, en el built-in puedes considerar este ejemplo:

int* pInt = NULL; int* ptrC = new int[1]; ptrC[0] = 1; pInt = ptrC; for(int i = 0; i < 1; i++) cout << pInt[i] << ", "; cout << endl; int* ptrD = new int[3]; ptrD[0] = 5; ptrD[1] = 7; ptrD[2] = 77; *pInt = *ptrD; // copying values of ptrD to a pointer which point to an array of only one element! // the correct way: // pInt = ptrD; for(int i = 0; i < 3; i++) cout << pInt[i] << ", "; cout << endl;

por lo tanto, el resultado no es como usted adivina.


DESCARGO DE RESPONSABILIDAD: El código aquí se proporciona como un medio para comprender una idea, no para implementarse en producción.

Estás usando herencia. Puede lograr 3 cosas:

  • Agregar campos
  • Agregar métodos
  • reemplazar los métodos virtuales

De todas esas características, estás utilizando solo la última. Esto significa que no estás obligado a depender de la herencia. Puede obtener los mismos resultados por muchos otros medios. Lo más simple es mantener las pestañas en el "tipo" por ti mismo, esto te permitirá cambiarlo sobre la marcha:

#include <stdexcept> enum MyType { BASE, DERIVED }; class Any { private: enum MyType type; public: void whoami() { switch(type){ case BASE: std::cout << "I am Base/n"; return; case DERIVED: std::cout << "I am Derived/n"; return; } throw std::runtime_error( "undefined type" ); } void changeType(MyType newType){ //insert some checks if that kind of transition is legal type = newType; } Any(MyType initialType){ type = initialType; } };

Sin herencia, el "tipo" es tuyo para hacer lo que quieras. Puede changeType en cualquier momento que le convenga. Con ese poder también viene la responsabilidad: el compilador ya no se asegurará de que el tipo sea correcto o siquiera esté configurado. Tienes que asegurarte o tendrás problemas para depurar los errores de tiempo de ejecución.

Usted también puede envolverlo en herencia, por ej. para obtener un reemplazo directo para el código existente:

class Base : Any { public: Base() : Any(BASE) {} }; class Derived : public Any { public: Derived() : Any(DERIVED) {} };

O (un poco más feo):

class Derived : public Base { public: Derived : Base() { changeType(DERIVED) } };

Esta solución es fácil de implementar y fácil de entender. Pero con más opciones en el conmutador y más código en cada ruta, se vuelve muy desordenado. Entonces, el primer paso es refactorizar el código real fuera del interruptor y convertirlo en funciones autónomas. ¿Dónde mejor guardar que otra clase que no sea Derivied ?

class Base { public: static whoami(Any* This){ std::cout << "I am Base/n"; } }; class Derived { public: static whoami(Any* This){ std::cout << "I am Derived/n"; } }; /*you know where it goes*/ switch(type){ case BASE: Base:whoami(this); return; case DERIVED: Derived:whoami(this); return; }

¡Entonces puede reemplazar el conmutador por una clase externa que lo implemente mediante herencia virtual y TADA! Hemos reinventado el patrón de estrategia, como otros han dicho en primer lugar:)

La conclusión es: haga lo que haga, no heredará la clase principal.