que - Referencias polimórficas en C++
programacion orientada a objetos pdf (7)
Me preguntaba cómo se puede hacer polimorfismo con referencias, en lugar de punteros.
Para aclarar, ver el siguiente ejemplo mínimo:
class A;
class B {
public:
A& a; ///////////////// <- #1
B();
void doStuff();
};
class A {
public:
virtual void doSmth() = 0;
};
void B::doStuff() {
a.doSmth();
}
class A1 : public A {
public:
void doSmth() {
}
};
B::B() : a(
* ////////////// <- #2
(new A1) /////////// <- #3
) {
}
Esto compila y funciona, pero como el punto más importante aquí es que a
en la línea #1
es una referencia, por lo que para poder usarla de manera polimórfica (¿es esa una palabra real?), Como se muestra en la línea #3
, tengo para "convertir un puntero a una referencia" mediante la eliminación de referencias.
Esto me parece un poco extraño, y me preguntaba si hay una manera mejor (en el sentido de más limpia ). ¿Se trata sólo de mí?
Razón fundamental
Sería fantástico si no necesitara una new
, pero cuando declaro (!) B
no tengo idea de cómo crear una instancia de A1
(!) Ya que A
es una declaración de reenvío: A1
se implementa de la misma manera Unidad de compilación como B
Aún así, ¿hay una necesidad real de asignación de memoria dinámica en este caso? ¿Cómo harías esto?
Lo siento por la pregunta un poco doble.
Editar
Nota: B
es enorme (y no puedo crear una clase de plantilla), y quedará fuera del alcance precisamente cuando finalice el programa; a
es pequeño y hace que dos módulos grandes se comuniquen entre sí, será necesario siempre que La instancia de B
vive (solo hay una).
Editar 2
Acabo de darme cuenta de que, dado que tanto A
como B
son efectivamente singletons, simplemente puedo crear una instancia static
de A1
en la unidad de compilación de B
, evitando la asignación de memoria dinámica (incluso si hubiera dos B
s, podrían usar fácilmente la misma instancia de A
). Para ser justos, no publiqué esto como respuesta, pero aceptaré la respuesta que me impulsó a encontrar esta solución .
Aún así, ¿hay una necesidad real de asignación de memoria dinámica en este caso?
No. Solo defina A1 primero y luego conviértalo en un miembro normal de B.
El polimorfismo funciona bien tanto con referencias como con punteros.
Aún así, ¿hay una necesidad real de asignación de memoria dinámica en este caso?
Ya sea la asignación de memoria dinámica o inyectar la referencia en el ctor de B.
Erm, ¿no es esto suficiente?
#include <iostream>
struct A;
struct B
{
B(A& a);
void foo();
A& _a;
};
struct A
{
virtual void foo() =0;
};
struct A1 : public A
{
virtual void foo() { std::cout << "A1::foo" << std::endl; }
};
B::B(A& a) : _a(a) {}
void B::foo() { _a.foo(); }
int main(void)
{
A1 a; // instance of A1
B b(a); // construct B with it
b.foo();
}
Esto es de hecho un poco extraño. Si desea una variable miembro de tipo A1
(en lugar de una referencia), ¿por qué no reorganizar su código para que la definición de A1
aparezca antes de la definición de B
?
Me doy cuenta de que esta es una publicación muy antigua, pero hay otra opción que tiene para manejar las referencias de los objetos asignados dinámicamente. Puede asignar una referencia al objeto asignado dinámicamente. A continuación se muestra un código ficticio para darle una idea de cómo funciona esto.
struct A
{
int b;
virtual void print();
A(int val):b(val) {}
};
struct A_child:public A
{
A_child(int val):A(val) {}
void print();
};
void A:print()
{
cout<<"parent/n";
}
void A_child:print()
{
cout<<"child/n";
}
struct test_ref
{
A *& ref;
test_ref(A * ptr) : ref(ptr)
}
int main()
{
test_ref parent(new A(12));
parent.ref->print();
test_ref child(new A_child(15));
child.ref->print();
}
Para ser honesto, no estoy seguro de cuándo es una buena idea. Solo quería mostrar un enfoque alternativo en el que no tiene que eliminar la referencia a la memoria asignada dinámicamente al inicializar un objeto.
También estoy bastante seguro de que asignar dinámicamente un puntero al inicializar una clase en la que el puntero se almacena como un puntero de referencia probablemente dará lugar a una pérdida de memoria a menos que pueda eliminar el puntero de referencia.
No es difícil imaginar por qué las referencias pueden funcionar de forma polimórfica como punteros (por no mencionar que las referencias a menudo se implementan como punteros). Aquí hay un ejemplo rápido:
class Base {
public:
virtual void something() { }
};
class Derived : public Base {
public:
void something() { }
};
Base& foo() {
static Derived d;
return d;
}
foo().something(); // calls Derived''s something
Además, ¿por qué asigna memoria dinámica para una referencia? Probablemente no debería utilizar una referencia en este caso en absoluto. Además, las clases de escritura con miembros de referencia impiden efectivamente la asignación (como escuché a alguien decir bastante bien).
No hay nada extraño. Los polimorfismos funcionan tanto para punteros como para referencias:
struct Base { };
struct Derived : Base;
void foo(Base &);
int main() {
Derived x;
foo(x); // fine
}
Está combinando esto con otro problema, a saber, creando una referencia a un objeto dinámico:
T * pt = new T;
T & rt = *pt;
T & x = *new T; // same effect
Tenga en cuenta que generalmente es un estilo muy malo rastrear un objeto dinámico solo por referencia, porque la única forma de eliminarlo es a través de delete &x;
, y es muy difícil ver que x
necesita ser limpiado.
Hay dos alternativas inmediatas para su diseño: 1) haga a
objeto miembro en B
, o 2) haga un shared_ptr<A>
o unique_ptr<A>
y cambie el inicializador a a(new A1)
. Todo depende de si realmente necesita el comportamiento polimórfico, es decir, si tiene otros constructores para B
que asignan una clase derivada diferente a otra que no sea A1
.