¿Por qué los constructores no se heredan?

33

Estoy confundido sobre cuáles podrían ser los problemas si un constructor fuera heredado de una clase base. Cpp Primer Plus dice:

Los constructores son diferentes de otros métodos de clase en que crean nuevos objetos, mientras que otros métodos son invocados por objetos existentes . Esta es una razón por la que los constructores no se heredan . La herencia significa que un objeto derivado puede usar un método de clase base, pero, en el caso de los constructores, el objeto no existe hasta después de que el constructor haya hecho su trabajo.

Entiendo que se llama al constructor antes de que se complete la construcción del objeto.

¿Cómo puede conducir a problemas si una clase secundaria hereda ( al heredar quiero decir que la clase secundaria puede anular el método de la clase primaria, etc. No solo acceder al método de la clase primaria ) el constructor primario?

Entiendo que no hay necesidad de llamar explícitamente a un constructor dentro de un código [no que yo sepa todavía.] Excepto al crear objetos. Incluso entonces puede hacerlo utilizando algún mecanismo para invocar el constructor principal [En cpp, usando ::o usando member initialiser list, En java usando super]. En Java hay una aplicación para llamarlo en la primera línea, entiendo que es para asegurarse de que el objeto primario se crea primero y luego continúa la construcción del objeto secundario.

Puede anularlo . Pero no puedo encontrar situaciones en las que esto pueda plantear un problema. Si el hijo hereda el constructor principal, ¿qué puede salir mal?

Entonces, esto es solo para evitar heredar funciones innecesarias. ¿O hay más?

Suvarna Pattayil
fuente
No está realmente al día con C ++ 11, pero creo que hay algún tipo de herencia de constructor.
Yannis
3
Tenga en cuenta que haga lo que haga en constructores, debe tener cuidado con los destructores.
Manoj R
2
Creo que necesitas definir qué se entiende por "heredar un constructor". Todas las respuestas parecen tener variaciones sobre lo que piensan que significa "heredar un constructor". No creo que ninguno de ellos coincida con mi interpretación inicial. La descripción "en el cuadro gris" de arriba "Herencia significa que un objeto derivado puede usar un método de clase base" implica que los constructores son heredados. Un objeto derivado ciertamente puede y utiliza métodos de constructor de clase base, SIEMPRE.
Dunk
1
Gracias por la aclaración de heredar un constructor, ahora puede obtener algunas respuestas.
Dunk

Respuestas:

41

No puede haber una herencia adecuada de constructores en C ++, porque el constructor de una clase derivada necesita realizar acciones adicionales que un constructor de clase base no tiene que hacer y no conoce. Estas acciones adicionales son la inicialización de los miembros de datos de la clase derivada (y, en una implementación típica, también configuran el vpointer para que haga referencia a las clases derivadas vtable).

Cuando se construye una clase, hay una serie de cosas que siempre deben suceder: los constructores de la clase base (si los hay) y los miembros directos necesitan ser llamados y si hay alguna función virtual, el vpointer debe estar configurado correctamente . Si usted no proporciona un constructor para su clase, el compilador será crear uno que lleva a cabo las acciones necesarias y nada más. Si haces proporcionar un constructor, pero se pierda en algunas de las acciones requeridas (por ejemplo, la inicialización de algunos miembros), entonces el compilador añadirá automáticamente las acciones que faltan a su constructor. De esta manera, el compilador asegura que cada clase tenga al menos un constructor y que cada constructor inicialice completamente los objetos que crea.

En C ++ 11, se ha introducido una forma de 'herencia de constructor' donde puede indicarle al compilador que genere un conjunto de constructores para usted que tome los mismos argumentos que los constructores de la clase base y que simplemente envíe esos argumentos a clase base
Aunque oficialmente se llama herencia, no lo es realmente porque todavía hay una función específica de clase derivada. Ahora solo lo genera el compilador en lugar de ser escrito explícitamente por usted.

Esta característica funciona así:

struct Base {
    Base(int a) : i(a) {}
    int i;
};

struct Derived : Base {
    Derived(int a, std::string s) : Base(a), m(s) {}

    using Base::Base; // Inherit Base's constructors.
    // Equivalent to:
    //Derived(int a) : Base(a), m() {}

    std::string m;
};

Derivedahora tiene dos constructores (sin contar los constructores copiar / mover). Uno que toma un int y una cadena y otro que toma solo un int.

Bart van Ingen Schenau
fuente
Entonces, ¿esto supone que la clase hija no tendrá su propio constructor? y el constructor heredado obviamente no tiene conocimiento de los miembros secundarios y las inicializaciones que se deben realizar
Suvarna Pattayil
C ++ se define de tal manera que es imposible que una clase no tenga sus propios constructores. Es por eso que no considero que la "herencia del constructor" sea realmente una herencia. Es solo una forma de evitar escribir tediosas repeticiones.
Bart van Ingen Schenau
2
Creo que la confusión se debe al hecho de que incluso un constructor vacío hace un trabajo detrás de escena. Entonces el constructor realmente tiene dos partes: los trabajos internos y la parte que escribes. Como no están bien separados, los constructores no se heredan.
Sarien
1
Considere indicar de dónde m()viene y cómo cambiaría si su tipo fuera, por ejemplo int.
Deduplicador el
¿Es posible hacerlo using Base::Baseimplícitamente? Tendría grandes consecuencias si olvidara esa línea en una clase derivada y necesito heredar el constructor en todas las clases derivadas
Post Self
7

No está claro qué quiere decir con "heredar el constructor principal". Utiliza la palabra anulación , lo que sugiere que puede estar pensando en constructores que se comportan como funciones virtuales polimórficas . Deliberadamente, no uso el término "constructores virtuales" porque ese es un nombre común para un patrón de código en el que realmente se requiere una instancia ya existente de un objeto para crear otro.

Hay poca utilidad para los constructores polimórficos fuera del patrón de "constructor virtual", y es difícil encontrar un escenario concreto en el que se pueda usar un constructor polimórfico real. Un ejemplo muy ingenioso que de ninguna manera es incluso C ++ remotamente válido :

struct Base {
  virtual Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) override {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Base(p1, p2); // This might call Derived(unsigned, unsigned).
  Derived *p_d2 = nullptr;
  p_d2 = new Base(p1, p2); // This might call Derived(unsigned, unsigned) too.
}

En este caso, el constructor llamado depende del tipo concreto de la variable que se construye o asigna. Es complejo de detectar durante la generación de análisis / código y no tiene utilidad: conoce el tipo concreto que está construyendo y ha escrito un constructor específico para la clase derivada. El siguiente código válido de C ++ hace exactamente lo mismo, es un poco más corto y es más explícito en lo que hace:

struct Base {
  Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Derived(p1, p2); 
  Derived *p_d2 = nullptr;
  p_d2 = new Derived(p1, p2);
}


Una segunda interpretación o quizás una pregunta adicional es: ¿qué pasaría si los constructores de la clase Base estuvieran presentes automáticamente en todas las clases derivadas a menos que se ocultaran explícitamente?

Si el hijo hereda el constructor principal, ¿qué puede salir mal?

Tendría que escribir código adicional para ocultar un constructor principal que sea incorrecto para construir la clase derivada. Esto puede suceder cuando la clase derivada especializa la clase base de tal manera que ciertos parámetros se vuelven irrelevantes.

El ejemplo típico es rectángulos y cuadrados (tenga en cuenta que los cuadrados y rectángulos generalmente no son sustituibles por Liskov, por lo que no es un diseño muy bueno, pero resalta el problema).

struct Rectangle {
  Rectangle(unsigned width, unsigned height) {...}
};

struct Square : public Rectangle {
  explicit Square(unsigned side) : Rectangle(side, side) {...}
};

Si Square heredó el constructor de dos valores de Rectangle, podría construir cuadrados con una altura y ancho diferentes ... Eso es lógicamente incorrecto, por lo que querrá ocultar ese constructor.

Joris Timmermans
fuente
3

Por qué no se heredan los constructores: la respuesta es sorprendentemente simple: el constructor de la clase base "construye" la clase base y el constructor de la clase heredada "construye" la clase heredada. Si la clase heredada heredaría el constructor, el constructor intentaría construir un objeto de tipo de clase base y usted no podría "construir" un objeto de tipo de clase heredada.

Qué tipo de derrota el propósito de inherente a una clase.

Pieter B
fuente
3

El problema más obvio al permitir que la clase derivada anule el constructor de la clase base es que el desarrollador de la clase derivada ahora es responsable de saber cómo construir su (s) clase (s) base (s). ¿Qué sucede cuando la clase derivada no construye la clase base correctamente?

Además, el principio de sustitución de Liskov ya no se aplicaría, ya que no puede contar con que su colección de objetos de clase base sea compatible entre sí, porque no hay garantía de que la clase base se haya construido de manera adecuada o compatible con los otros tipos derivados.

Se complica aún más cuando se agrega más de 1 nivel de herencia. Ahora su clase derivada necesita saber cómo construir todas las clases base en la cadena.

Entonces, ¿qué sucede si agrega una nueva clase base a la parte superior de la jerarquía de herencia? Tendría que actualizar todos los constructores de clase derivados.

Remojar
fuente
2

Los constructores son fundamentalmente diferentes de otros métodos:

  1. Se generan si no los escribe.
  2. Todos los constructores de clase base se llaman implícitamente incluso si no lo hace manualmente
  3. No los llamas explícitamente sino creando objetos.

Entonces, ¿por qué no se heredan? Respuesta simple: porque siempre hay una anulación, ya sea generada o escrita manualmente.

¿Por qué cada clase necesita un constructor? Esa es una pregunta complicada y creo que la respuesta depende del compilador. Existe un constructor "trivial" para el que el compilador no exige que se llame como parece. Creo que eso es lo más parecido a lo que quiere decir con herencia, pero por las tres razones mencionadas anteriormente, creo que comparar constructores con métodos normales no es realmente útil. :)

Sarien
fuente
1

Cada clase necesita un constructor, incluso los predeterminados.
C ++ creará constructores predeterminados para usted, excepto si crea un constructor especializado.
En caso de que su clase base use un constructor especializado, deberá escribir el constructor especializado en la clase derivada incluso si ambos son iguales y encadenarlos.
C ++ 11 le permite evitar la duplicación de código en constructores usando :

Class A : public B {
using B:B;
....
  1. Un conjunto de constructores heredados se compone de

    • Todos los constructores no de plantilla de la clase base (después de omitir los parámetros de puntos suspensivos, si los hay) (desde C ++ 14)
    • Para cada constructor con argumentos predeterminados o el parámetro de puntos suspensivos, todas las firmas de constructor que se forman al eliminar los puntos suspensivos y omitir los argumentos predeterminados de los extremos de los argumentos se enumeran uno por uno
    • Todas las plantillas de constructor de la clase base (después de omitir los parámetros de puntos suspensivos, si los hay) (desde C ++ 14)
    • Para cada plantilla de constructor con argumentos predeterminados o puntos suspensivos, todas las firmas de constructor que se forman al soltar los puntos suspensivos y omitir los argumentos predeterminados de los extremos de los argumentos se enumeran uno por uno.
  2. Todos los constructores heredados que no son el constructor predeterminado o el constructor copiar / mover y cuyas firmas no coinciden con los constructores definidos por el usuario en la clase derivada, se declaran implícitamente en la clase derivada. Los parámetros predeterminados no se heredan

MeduZa
fuente
0

Puedes usar:

MyClass() : Base()

¿Estás preguntando por qué tienes que hacer esto?

La subclase podría tener propiedades adicionales que podrían necesitar inicializarse en el constructor, o podría inicializar las variables de la clase base de una manera diferente.

¿De qué otra forma crearías el objeto de subtipo?

Tom Tom
fuente
Gracias. En realidad, estoy tratando de entender por qué un constructor no se hereda en la clase secundaria, como otros métodos de clase primaria.
Suvarna Pattayil