puro - polimorfismo por sobrecarga c++
¿Por qué no tenemos un constructor virtual en C++? (20)
A diferencia de los lenguajes orientados a objetos como Smalltalk o Python, donde el constructor es un método virtual del objeto que representa a la clase (lo que significa que no necesita el patrón de fábrica abstracto de GoF, ya que puede pasar el objeto que representa a la clase en lugar de hacer el suyo propio), C ++ es un lenguaje basado en clases, y no tiene objetos que representen ninguna de las construcciones del lenguaje. La clase no existe como un objeto en tiempo de ejecución, por lo que no puede llamar a un método virtual.
Esto encaja con la filosofía de "no pagas por lo que no usas", aunque cada proyecto grande de C ++ que he visto ha terminado implementando algún tipo de resumen de fábrica o reflexión.
¿Por qué C ++ no tiene un constructor virtual?
Aunque el concepto de constructores virtuales no encaja bien ya que el tipo de objeto es un requisito previo para la creación del objeto, no está completamente descartado.
El patrón de diseño de ''método de fábrica'' de GOF hace uso del ''concepto'' de constructor virtual, que es práctico en ciertas situaciones de diseño.
Cuando las personas hacen una pregunta como esta, me gusta pensar "¿qué pasaría si esto fuera realmente posible?" Realmente no sé qué significaría esto, pero supongo que tendría algo que ver con poder anular la implementación del constructor en función del tipo dinámico del objeto que se está creando.
Veo una serie de problemas potenciales con esto. Por un lado, la clase derivada no se construirá completamente en el momento en que se llama al constructor virtual, por lo que hay problemas potenciales con la implementación.
En segundo lugar, ¿qué pasaría en el caso de herencia múltiple? Su constructor virtual se llamaría varias veces, presumiblemente, entonces tendría que tener alguna forma de saber a cuál se estaba llamando.
En tercer lugar, en términos generales en el momento de la construcción, el objeto no tiene la tabla virtual completamente construida, esto significa que requeriría un gran cambio en la especificación del lenguaje para permitir el hecho de que el tipo dinámico del objeto sería conocido en la construcción hora. Esto permitiría entonces al constructor de la clase base llamar a otras funciones virtuales en el momento de la construcción, con un tipo de clase dinámica no completamente construido.
Finalmente, como alguien más ha señalado, puede implementar un tipo de constructor virtual utilizando funciones de tipo "crear" o "init" estáticas que básicamente hacen lo mismo que un constructor virtual.
Dejando de lado las razones semánticas, no hay vtable hasta que el objeto se haya construido, lo que hace que una designación virtual sea inútil.
El Vpointer se crea en el momento de la creación del objeto. vpointer no existe antes de la creación del objeto. por lo que no tiene sentido hacer que el constructor sea virtual.
El constructor virtual de C ++ no es posible. Por ejemplo, no puede marcar un constructor como virtual. Pruebe este código
#include<iostream.h>
using namespace std;
class aClass
{
public:
virtual aClass()
{
}
};
int main()
{
aClass a;
}
Provoca un error. Este código intenta declarar que un constructor es virtual. Ahora tratemos de entender por qué usamos palabras clave virtuales. La palabra clave virtual se utiliza para proporcionar polimorfismo en tiempo de ejecución. Por ejemplo prueba este código.
#include<iostream.h>
using namespace std;
class aClass
{
public:
aClass()
{
cout<<"aClass contructor/n";
}
~aClass()
{
cout<<"aClass destructor/n";
}
};
class anotherClass:public aClass
{
public:
anotherClass()
{
cout<<"anotherClass Constructor/n";
}
~anotherClass()
{
cout<<"anotherClass destructor/n";
}
};
int main()
{
aClass* a;
a=new anotherClass;
delete a;
getchar();
}
En main a=new anotherClass;
asigna una memoria para anotherClass
en un puntero a
declara como tipo de aClass
. Esto hace que tanto el constructor (In aClass
como anotherClass
) llamen automáticamente. Por lo tanto, no necesitamos marcar el constructor como virtual. Porque cuando se crea un objeto, debe seguirlo. la cadena de creación (es decir, primero la base y luego las clases derivadas). Pero cuando tratamos de eliminar un delete a;
hace que solo se llame al destructor base. Por lo tanto, tenemos que manejar el destructor usando una palabra clave virtual. Así que el constructor virtual no es posible, pero el destructor virtual es .
El mecanismo virtual solo funciona cuando tiene un puntero de clase basado en un objeto de clase derivado. La construcción tiene sus propias reglas para llamar a los constructores de la clase base, básicamente la clase base a la derivada. ¿Cómo podría ser útil o llamado un constructor virtual? No sé qué hacen otros idiomas, pero no puedo ver cómo un constructor virtual podría ser útil o incluso implementado. La construcción debe haber tenido lugar para que el mecanismo virtual tenga sentido y la construcción también debe haber tenido lugar para que se hayan creado las estructuras de la tabla que proporcionan la mecánica del comportamiento polimórfico.
Escuchalo desde la boca del caballo :).
Preguntas frecuentes sobre el estilo y la técnica en C ++ de Bjarne Stroustrup ¿Por qué no tenemos constructores virtuales?
Una llamada virtual es un mecanismo para realizar el trabajo dada información parcial. En particular, "virtual" nos permite llamar a una función conociendo solo las interfaces y no el tipo exacto del objeto. Para crear un objeto necesitas información completa. En particular, necesita saber el tipo exacto de lo que desea crear. En consecuencia, una "llamada a un constructor" no puede ser virtual.
La entrada de Preguntas frecuentes continúa para proporcionar el código de una manera de lograr este fin sin un constructor virtual.
Hay una razón muy básica: los constructores son efectivamente funciones estáticas, y en C ++ ninguna función estática puede ser virtual.
Si tiene mucha experiencia con C ++, sabe toda la diferencia entre las funciones estáticas y las funciones miembro. Las funciones estáticas están asociadas con la CLASE, no con los objetos (instancias), por lo que no ven un puntero "este". Solo las funciones miembro pueden ser virtuales, porque vtable, la tabla oculta de punteros de función que hace que el trabajo sea "virtual", es realmente un miembro de datos de cada objeto.
Ahora, ¿cuál es el trabajo del constructor? Está en el nombre: un constructor "T" inicializa los objetos T a medida que se asignan. Esto automáticamente excluye que sea una función miembro! Un objeto tiene que EXISTIR antes de tener un puntero "this" y, por lo tanto, un vtable. Eso significa que incluso si el lenguaje tratara a los constructores como funciones ordinarias (no lo hace, por razones relacionadas en las que no entraré), tendrían que ser funciones miembro estáticas.
Una buena manera de ver esto es observar el patrón "Fábrica", especialmente las funciones de fábrica. Hacen lo que está buscando, y notará que si la clase T tiene un método de fábrica, SIEMPRE ES ESTÁTICO. Tiene que ser.
Las funciones virtuales básicamente proporcionan un comportamiento polimórfico. Es decir, cuando trabaja con un objeto cuyo tipo dinámico es diferente al tipo estático (tiempo de compilación) con el que se hace referencia, proporciona un comportamiento que es apropiado para el tipo real de objeto en lugar del tipo estático del objeto.
Ahora trata de aplicar ese tipo de comportamiento a un constructor. Cuando construyes un objeto, el tipo estático es siempre el mismo que el tipo de objeto real ya que:
Para construir un objeto, un constructor necesita el tipo exacto del objeto que es crear. Además, [...] no puede tener un puntero a un constructor
(Bjarne Stroustup (P424 El lenguaje de programación C ++ SE))
Las funciones virtuales en C ++ son una implementación del polimorfismo en tiempo de ejecución, y funcionarán como anulación de funciones. Generalmente, la palabra clave virtual
se usa en C ++ cuando se necesita un comportamiento dinámico. Sólo funcionará cuando exista el objeto. Mientras que los constructores se utilizan para crear los objetos. Los constructores serán llamados en el momento de la creación del objeto.
Entonces, si crea el constructor como virtual
, según la definición de la palabra clave virtual, debe tener un objeto existente para usar, pero el constructor se utiliza para crear el objeto, por lo que este caso nunca existirá. Así que no debes usar el constructor como virtual.
Por lo tanto, si intentamos declarar el compilador del constructor virtual, se produce un error:
Los constructores no pueden ser declarados virtuales
Las funciones virtuales se utilizan para invocar funciones basadas en el tipo de objeto apuntado por el puntero, y no el tipo de puntero en sí. Pero un constructor no es "invocado". Se llama una sola vez cuando se declara un objeto. Entonces, un constructor no puede hacerse virtual en C ++.
Lo hacemos, simplemente no es un constructor :-)
struct A {
virtual ~A() {}
virtual A * Clone() { return new A; }
};
struct B : public A {
virtual A * Clone() { return new B; }
};
int main() {
A * a1 = new B;
A * a2 = a1->Clone(); // virtual construction
delete a2;
delete a1;
}
No podemos simplemente decirlo como ... No podemos heredar constructores. Por lo tanto, no tiene sentido declararlos virtuales porque lo virtual proporciona polimorfismo.
Puede encontrar un ejemplo y la razón técnica por la que no está permitido en la respuesta de @stefan. Ahora una respuesta lógica a esta pregunta de acuerdo a mí es:
El uso principal de la palabra clave virtual es habilitar el comportamiento polimórfico cuando no sabemos a qué tipo de objeto apuntará el puntero de la clase base.
Pero pensar en esto es una forma más primitiva, para usar la funcionalidad virtual necesitará un puntero. ¿Y qué requiere un puntero? Un objeto al que apuntar! (considerando caso para la correcta ejecución del programa)
Por lo tanto, básicamente requerimos un objeto que ya existe en algún lugar de la memoria (no nos preocupa cómo se asignó la memoria, puede ser en tiempo de compilación o en tiempo de ejecución) para que nuestro puntero pueda apuntar correctamente a ese objeto.
Ahora, piense en la situación sobre el momento en que se asigna algo de memoria al objeto de la clase a la que se va a apuntar -> ¡Se llamará automáticamente a su constructor en esa instancia!
Así que podemos ver que realmente no tenemos que preocuparnos de que el constructor sea virtual, porque en cualquiera de los casos desea utilizar un comportamiento polimórfico, nuestro constructor ya se habría ejecutado ¡haciendo que nuestro objeto esté listo para su uso!
Se crea una tabla virtual (vtable) para cada Clase que tiene una o más ''funciones virtuales''. Cada vez que se crea un objeto de dicha clase, contiene un ''puntero virtual'' que apunta a la base de vtable correspondiente. Siempre que hay una llamada de función virtual, vtable se utiliza para resolver la dirección de la función. El constructor no puede ser virtual, porque cuando se ejecuta el constructor de una clase no hay vtable en la memoria, significa que no hay un puntero virtual definido aún. Por lo tanto el constructor siempre debe ser no virtual.
Si piensa lógicamente cómo funcionan los constructores y cuál es el significado / uso de una función virtual en C ++, entonces se dará cuenta de que un constructor virtual no tendría sentido en C ++. Declarar algo virtual en C ++ significa que puede ser anulado por una subclase de la clase actual, sin embargo, se llama al constructor cuando se crea el objeto, en ese momento no puede estar creando una subclase de la clase, debe ser creando la clase para que nunca haya necesidad de declarar un constructor virtual.
Y otra razón es que los constructores tienen el mismo nombre que su nombre de clase y si declaramos constructor como virtual, entonces debería ser redefinido en su clase derivada con el mismo nombre, pero no puede tener el mismo nombre de dos clases. Por eso no es posible tener un constructor virtual.
Tampoco debes llamar a la función virtual dentro de tu constructor. Consulte: http://www.artima.com/cppsource/nevercall.html
Además, no estoy seguro de que realmente necesite un constructor virtual. Puede lograr una construcción polimórfica sin ella: puede escribir una función que construirá su objeto de acuerdo con los parámetros necesarios.
dos razones que se me ocurren
Razon tecnica
El objeto existe solo después de que el constructor finalice. Para que el constructor se envíe utilizando la tabla virtual, tiene que haber un objeto existente con un puntero a la tabla virtual, pero ¿cómo puede existir un puntero a la tabla virtual si el objeto? todavía no existe? :)
Razon logica
Utiliza la palabra clave virtual cuando quiere declarar un comportamiento algo polimórfico. Pero no hay nada polimórfico con los constructores, el trabajo de los constructores en C ++ es simplemente poner datos de un objeto en la memoria. Dado que las tablas virtuales (y el polimorfismo en general) tienen que ver con el comportamiento polimórfico más que con los datos polimórficos, no tiene sentido declarar un constructor virtual.
Resumen : el estándar de C ++ podría especificar una notación y un comportamiento para "constructores virtuales" que sea razonablemente intuitivo y no demasiado difícil de soportar para los compiladores, pero ¿por qué realizar un cambio estándar para esto específicamente cuando la funcionalidad ya se puede implementar limpiamente utilizando create()
/ clone()
(ver abajo)? No es tan útil como muchas otras propuestas lingüísticas en tramitación.
Discusión
Vamos a postular un mecanismo de "constructor virtual":
Base* p = new Derived(...);
Base* p2 = new p->Base(); // possible syntax???
En lo anterior, la primera línea construye un objeto Derived
, de modo que la tabla de envío virtual de *p
puede suministrar razonablemente un "constructor virtual" para usar en la segunda línea. (Decenas de respuestas en esta página que indican que "el objeto aún no existe, por lo que la construcción virtual es imposible" se enfocan de manera innecesaria en el objeto que se construirá).
La segunda línea postula la notación new p->Base()
para solicitar la asignación dinámica y la construcción predeterminada de otro objeto Derived
.
Notas:
el compilador debe organizar la asignación de memoria antes de llamar al constructor ; los constructores normalmente admiten la asignación automática (informalmente "apilar"), estática (para el alcance global / espacio de nombres y objetos
static
clase / función) y dinámica ("informalmente" "montón") cuando esnew
es usadoel tamaño del objeto que debe construirse
p->Base()
generalmente no se puede conocer en tiempo de compilación, por lo que la asignación dinámica es el único enfoque que tiene sentido- es posible asignar cantidades de memoria especificadas por el tiempo de ejecución en la pila, por ejemplo, la extensión de matriz de longitud variable de GCC ,
alloca()
, pero conduce a importantes ineficiencias y complejidades (por ejemplo, here y here respectivamente)
- es posible asignar cantidades de memoria especificadas por el tiempo de ejecución en la pila, por ejemplo, la extensión de matriz de longitud variable de GCC ,
para la asignación dinámica debe devolver un puntero para que la memoria se pueda
delete
d más tarde.La notación postulada enumera explícitamente lo
new
para enfatizar la asignación dinámica y el tipo de resultado de puntero.
El compilador necesitaría:
- descubra cuánta memoria se necesita
Derived
, ya sea llamando a una función de tamañovirtual
implícita o teniendo dicha información disponible a través de RTTI - llamar al
operator new(size_t)
para asignar memoria - Invoca
Derived()
con lanew
colocación.
O
- cree una entrada adicional para una función que combina asignación dinámica y construcción
Entonces, no parece insuperable especificar e implementar constructores virtuales, pero la pregunta del millón es: ¿cómo sería mejor que lo que es posible usar las características existentes del lenguaje C ++ ...? Personalmente, no veo ningún beneficio sobre la solución a continuación.
`clone ()` y `create ()`
Las preguntas frecuentes de C ++ documentan un lenguaje de "constructor virtual" , que contiene virtual
métodos virtual
create()
y clone()
para construir por defecto o copiar y construir un nuevo objeto asignado de forma dinámica:
class Shape {
public:
virtual ~Shape() { } // A virtual destructor
virtual void draw() = 0; // A pure virtual function
virtual void move() = 0;
// ...
virtual Shape* clone() const = 0; // Uses the copy constructor
virtual Shape* create() const = 0; // Uses the default constructor
};
class Circle : public Shape {
public:
Circle* clone() const; // Covariant Return Types; see below
Circle* create() const; // Covariant Return Types; see below
// ...
};
Circle* Circle::clone() const { return new Circle(*this); }
Circle* Circle::create() const { return new Circle(); }
También es posible cambiar o sobrecargar a create()
para aceptar argumentos, aunque para coincidir con la firma de la función virtual
la clase / interfaz base, los argumentos a los reemplazos deben coincidir exactamente con una de las sobrecargas de la clase base. Con estas facilidades explícitas proporcionadas por el usuario, es fácil agregar registros, instrumentación, alterar la asignación de memoria, etc.