¿Por qué no tenemos un constructor virtual en C ++?

¿Por qué C ++ no tiene un constructor virtual?

    Escúchalo de la boca del caballo :).

    De Bjarne Stroustrup’s C ++ Style and Technique FAQ ¿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 que conoce 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 quiere crear. En consecuencia, una “llamada a un constructor” no puede ser virtual.

    La entrada de Preguntas Frecuentes continúa para dar el código de una manera de lograr este fin sin un constructor virtual.

    Las funciones virtuales básicamente proporcionan un comportamiento polimórfico. Es decir, cuando trabaja con un objeto cuyo tipo dynamic es diferente del tipo estático (tiempo de comstackción) al que se lo refiere, proporciona un comportamiento que es apropiado para el tipo de objeto real en lugar del tipo estático del objeto.

    Ahora intenta aplicar ese tipo de comportamiento a un constructor. Cuando construyes un objeto, el tipo estático siempre es el mismo que el tipo de objeto real, ya que:

    Para construir un objeto, un constructor necesita el tipo exacto del objeto que debe crear […] Además, […] no puede tener un puntero a un constructor.

    (Bjarne Stroustup (P424 El lenguaje de progtwigción C ++ SE))

    A diferencia de los lenguajes orientados a objetos como Smalltalk o Python, donde el constructor es un método virtual del objeto que representa la clase (lo que significa que no necesita el patrón de fábrica abstracto GoF, ya que puede pasar el objeto que representa la clase en lugar de el suyo), C ++ es un lenguaje basado en clases, y no tiene objetos que representen ninguno de los constructos del lenguaje. La clase no existe como objeto en tiempo de ejecución, por lo que no puede invocar un método virtual en ella.

    Esto encaja con la filosofía de “no pagas por lo que no usas”, aunque cada gran proyecto de C ++ que he visto ha terminado implementando alguna forma de fábrica abstracta o reflection.

    dos razones por las que puedo pensar:

    Razón técnica

    El objeto existe solo después de que termina el constructor. 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 polimorfo con los constructores, el trabajo de los constructores en C ++ es simplemente poner un objeto de datos en la memoria. Dado que las tablas virtuales (y el polymorphism en general) tienen que ver con el comportamiento polimórfico y no con los datos polimórficos, no tiene sentido declarar un constructor virtual.

    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; } 

    Dejando aparte las razones semánticas, no hay vtable hasta después de la construcción del objeto, lo que hace que una designación virtual sea inútil.

    Resumen : el estándar C ++ podría especificar una notación y comportamiento para “constructores virtuales” que sea razonablemente intuitivo y no demasiado difícil para los comstackdores, pero ¿por qué hacer un cambio estándar específicamente cuando la funcionalidad ya puede implementarse limpiamente utilizando create() / clone() (ver a continuación)? No es tan útil como muchas otras propuestas de idiomas en preparació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 , por lo que la tabla de despacho virtual de *p puede proporcionar razonablemente un “constructor virtual” para usar en la segunda línea. (Docenas de respuestas en esta página que afirman que “el objeto aún no existe, por lo que la construcción virtual es imposible” se enfocan de forma innecesariamente miope 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 comstackdor debe orquestar la asignación de memoria antes de llamar al constructor ; los constructores normalmente admiten la asignación automática (informalmente “astackda”), estática (para ámbito global / espacio de nombres y clase- / función-objetos static ) y dinámica (informalmente “montón”) cuando es new es usado

      • el tamaño del objeto que se construirá por p->Base() generalmente no se puede conocer en tiempo de comstackción, por lo que la asignación dinámica es el único enfoque que tiene sentido

        • es posible asignar cantidades de memoria especificadas en tiempo de ejecución en la stack, por ejemplo, la extensión de matriz de longitud variable de GCC , alloca() , pero conduce a ineficiencias y complejidades significativas (por ejemplo, aquí y aquí, respectivamente)
    • para la asignación dinámica, debe devolver un puntero para que la memoria pueda delete más tarde.

    • la notación postulada enumera explícitamente new para enfatizar la asignación dinámica y el tipo de resultado del puntero.

    El comstackdor necesitaría:

    • averiguar cuánta memoria se necesita Derived , ya sea llamando a un sizeof función virtual implícito o teniendo dicha información disponible a través de RTTI
    • operator new(size_t) llamada operator new(size_t) para asignar memoria
    • invocar Derived() con colocación new .

    O

    • crear una entrada vtable 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 de lo que es posible utilizando las características del lenguaje C ++ existente …? Personalmente, no veo ningún beneficio sobre la solución a continuación.


    `clone ()` y `create ()`

    Las preguntas frecuentes de C ++ documentan un modismo de “constructor virtual” , que contiene virtual métodos virtual create() y clone() para construir por defecto o copiar-construir un nuevo objeto asignado dinámicamente:

     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 create() para aceptar argumentos, aunque para que coincida con la firma de la función virtual la clase base / interfaz, los argumentos para sobrescribir deben coincidir exactamente con una de las sobrecargas de la clase base. Con estas instalaciones explícitas proporcionadas por el usuario, es fácil agregar registro, instrumentación, alterar la asignación de memoria, etc.

    Si bien el concepto de constructores virtuales no encaja bien, dado que el tipo de objeto es un requisito previo para la creación de objetos, no está completamente superado.

    El patrón de diseño de ‘método de fábrica’ de GOF hace uso del ‘concepto’ de constructor virtual, que es manejable en ciertas situaciones de diseño.

    Las funciones virtuales se usan 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 solo una vez cuando se declara un objeto. Por lo tanto, un constructor no se puede hacer virtual en C ++.

    Tampoco debe llamar a la función virtual dentro de su constructor. Ver: http://www.artima.com/cppsource/nevercall.html

    Además, no estoy seguro de que realmente necesites un constructor virtual. Puede lograr la construcción polimórfica sin él: puede escribir una función que construirá su objeto de acuerdo con los parámetros necesarios.

    Cuando la gente hace una pregunta como esta, me gusta pensar “¿qué pasaría si esto fuera realmente posible?” Realmente no sé lo que esto significaría, pero creo que tendría algo que ver con poder anular la implementación del constructor en función del tipo dynamic del objeto que se está creando.

    Veo una serie de problemas potenciales con esto. Por un lado, la clase derivada no se construirá por completo en el momento en que se llama al constructor virtual, por lo que existen posibles problemas con la implementación.

    En segundo lugar, ¿qué pasaría en el caso de la herencia múltiple? Su constructor virtual sería llamado varias veces, presumiblemente, entonces necesitaría tener alguna forma de saber a cuál se llamaba.

    En tercer lugar, en términos generales al 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 dynamic del objeto se conocería en la construcción hora. Esto permitiría que el constructor de la clase base pueda llamar a otras funciones virtuales en el momento de la construcción, con un tipo de clase dinámica no completamente construida.

    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 haría un constructor virtual.

    Las funciones virtuales en C ++ son una implementación del polymorphism en tiempo de ejecución, y harán una función de sobreescritura. En general, la palabra clave virtual se usa en C ++ cuando se necesita un comportamiento dynamic. Solo funcionará cuando el objeto exista. Mientras que los constructores se usan para crear los objetos. Se llamarán constructores en el momento de la creación del objeto.

    Por lo tanto, si crea el constructor como virtual , según la definición de palabra clave virtual, debe tener un objeto existente para usar, pero el constructor está acostumbrado a crear el objeto, por lo que este caso nunca existirá. Entonces no deberías usar el constructor como virtual.

    Por lo tanto, si tratamos de declarar el comstackdor del constructor virtual genere un error:

    Los constructores no pueden ser declarados virtuales

    Puede encontrar un ejemplo y la razón técnica de por qué no está permitido en la respuesta de @stefan. Ahora, una respuesta lógica a esta pregunta según mi opinión es:

    El principal uso de la palabra clave virtual es habilitar el comportamiento polimórfico cuando no sabemos a qué tipo de objeto apunta el puntero de la clase base.

    Pero piense en esto de una manera más primitiva, para usar la funcionalidad virtual necesitará un puntero. ¿Y qué requiere un puntero? Un objeto para apuntar! (considerando el caso para la ejecución correcta del progtwig)

    Entonces, 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 estar en tiempo de comstackción o en tiempo de ejecución) para que nuestro puntero apunte correctamente a ese objeto.

    Ahora, piense en la situación sobre el momento en que al objeto de la clase que se apunta se le asigna cierta memoria -> ¡Su constructor será llamado automáticamente en esa instancia!

    Así que podemos ver que no tenemos que preocuparnos de que el constructor sea virtual, porque en cualquiera de los casos que desee utilizar un comportamiento polimórfico nuestro constructor ya se habrá ejecutado, ¡haciendo que nuestro objeto esté listo para su uso!

    El mecanismo virtual solo funciona cuando tienes un puntero de clase basado en un objeto de clase derivado. La construcción tiene sus propias reglas para la convocatoria de constructores de clase base, básicamente de clase base a derivada. ¿Cómo podría ser útil o llamar un constructor virtual? No sé lo que 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 algún sentido y la construcción también debe haber tenido lugar para que se hayan creado las estructuras vtable que proporciona la mecánica del comportamiento polimórfico.

    No podemos decirlo simplemente como … No podemos heredar constructores. Entonces no tiene sentido declararlos virtuales porque el virtual proporciona polymorphism.

    Se crea una tabla virtual (vtable) para cada Clase que tenga 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 del vtable correspondiente. Cada vez que hay una llamada de función virtual, el vtable se usa para resolver la dirección de la función. Constructor no puede ser virtual, porque cuando se ejecuta el constructor de una clase no hay vtable en la memoria, significa que aún no se ha definido un puntero virtual. Por lo tanto, el constructor siempre debe ser no virtual.

    El constructor virtual de C ++ no es posible. Por ejemplo, no puede marcar un constructor como virtual. Pruebe este código

     #include using namespace std; class aClass { public: virtual aClass() { } }; int main() { aClass a; } 

    Causa un error. Este código intenta declarar un constructor como virtual. Ahora intentemos entender por qué usamos palabra clave virtual. La palabra clave virtual se usa para proporcionar un polymorphism de tiempo de ejecución. Por ejemplo, prueba este código.

     #include 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 declarado como tipo de aClass . Esto provoca que el constructor (In aClass y anotherClass ) llame automáticamente. Por lo tanto, no es necesario marcar el constructor como virtual. Porque cuando se crea un objeto debe seguir 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 llame solo al destructor base. Por lo tanto, tenemos que manejar el destructor usando la palabra clave virtual. Entonces el constructor virtual no es posible, pero el destructor virtual es .Gracias

    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 tienes mucha experiencia con C ++, sabes todo acerca de la diferencia entre las funciones estática y de miembro. Las funciones estáticas están asociadas con CLASS, no con los objetos (instancias), por lo que no ven un puntero “this”. Solo las funciones de miembro pueden ser virtuales, porque la tabla vtable, la tabla oculta de punteros de función que hace que ‘virtual’ funcione, 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 que tenga 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 no entraré), tendrían que ser funciones de miembros estáticos.

    Una excelente manera de ver esto es observar el patrón “Fábrica”, especialmente las funciones de fábrica. Hacen lo que buscan, y notarán que si la clase T tiene un método de fábrica, SIEMPRE ES ESTÁTICA. Tiene que ser.

    El Vpointer se crea en el momento de la creación del objeto. vpointer no existe antes de la creación del objeto. así que no tiene sentido hacer que el constructor sea virtual.

    Si piensas lógicamente sobre cómo funcionan los constructores y cuál es el significado / uso de una función virtual en C ++, entonces te darás 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, cuando se crea el objetante se llama al constructor, en ese momento no se puede crear una subclase de la clase, se debe 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 el constructor como virtual, entonces debe redefinirse en su clase derivada con el mismo nombre, pero no puede tener el mismo nombre de dos clases. Por lo tanto, no es posible tener un constructor virtual.