2008-09-23 18 views
517

¿Cuáles son las reglas de C++ para llamar al constructor de la superclase desde una subclase?¿Cuáles son las reglas para llamar al constructor de la superclase?

Por ejemplo, sé que en Java, debe hacerlo como la primera línea del constructor de la subclase (y si no lo hace, se asume una llamada implícita a un superconstructor no arg, que le da un error de compilación si eso falta).

Respuesta

717

Los constructores de la clase base se llaman automáticamente si no tienen ningún argumento. Si desea llamar a un constructor de superclase con un argumento, debe usar la lista de inicialización del constructor de la subclase. A diferencia de Java, C++ admite herencia múltiple (para bien o para mal), por lo que la clase base debe ser referida por nombre, en lugar de "super()".

class SuperClass 
{ 
    public: 

     SuperClass(int foo) 
     { 
      // do something with foo 
     } 
}; 

class SubClass : public SuperClass 
{ 
    public: 

     SubClass(int foo, int bar) 
     : SuperClass(foo) // Call the superclass constructor in the subclass' initialization list. 
     { 
      // do something with bar 
     } 
}; 

Más información en la lista de inicialización del constructor here y here.

+36

quité 'explícito' del constructor de la superclase. A pesar de ser una mejor práctica para los constructores de argumento único, no estaba relacionado con la discusión en cuestión. Para obtener más información sobre la palabra clave explícita, consulte: http://weblogs.asp.net/kennykerr/archive/2004/08/31/Explicit-Constructors.aspx – luke

+1

colon: operador que utilizó para invocar el constructor de la clase superior antes de instanciar el elemento secundario constructor de clase, supongo que esto también es cierto para los métodos? – ha9u63ar

+2

@hagubear, solo válido para constructores, AFAIK – luke

14

Si tiene un constructor sin argumentos, se invocará antes de que se ejecute el constructor de la clase derivada.

Si desea llamar a una base de constructor con argumentos que tienen que escribir explícitamente que en el constructor derivado de la siguiente manera:

class base 
{ 
    public: 
    base (int arg) 
    { 
    } 
}; 

class derived : public base 
{ 
    public: 
    derived() : base (number) 
    { 
    } 
}; 

No se puede construir una clase derivada sin llamar a la padres constructor en C++. Eso sucede automáticamente si se trata de un C'tor que no es arg, sucede si llamas al constructor derivado directamente como se muestra arriba o tu código no se compilará.

16

La única manera de pasar valores a un constructor padre es a través de una lista de inicialización. La lista de iniciación se implementa con a: y luego una lista de clases y los valores que se pasarán al constructor de las clases.

Class2::Class2(string id) : Class1(id) { 
.... 
} 

También recuerde que si usted tiene un constructor que no toma ningún parámetro en la clase padre, se llama automáticamente antes de la constructora niño de ejecución.

174

En C++, los constructores sin argumento para todas las superclases y variables miembro son llamados por usted, antes de ingresar su constructor. Si desea pasarlos argumentos, hay una sintaxis diferente para esta llamada "encadenamiento constructor", que se ve así:

class Sub : public Base 
{ 
    Sub(int x, int y) 
    : Base(x), member(y) 
    { 
    } 
    Type member; 
}; 

En todo caso ejecutar en este momento lanza, las bases/miembros, que habían completado previamente la construcción tienen sus destructores llamados y la excepción se vuelve a lanzar a la persona que llama. Si se desea capturar excepciones durante el encadenamiento, debe utilizar un bloque de función intento:

class Sub : public Base 
{ 
    Sub(int x, int y) 
    try : Base(x), member(y) 
    { 
    // function body goes here 
    } catch(const ExceptionType &e) { 
    throw kaboom(); 
    } 
    Type member; 
}; 

De esta forma, cabe destacar que el bloque try es el cuerpo de la función, en lugar de estar en el interior del cuerpo de la función; esto le permite detectar excepciones lanzadas por inicializaciones implícitas o explícitas de miembros y clases base, así como durante el cuerpo de la función. Sin embargo, si un bloque catch de función no arroja una excepción diferente, el tiempo de ejecución volverá a generar el error original; excepciones durante la inicialización no se puede ignorar.

+1

No estoy seguro de entender la sintaxis de su segundo ejemplo ... ¿Es la construcción try/catch un reemplazo para el cuerpo constructor? – levik

+2

Sí. He modificado la sección y he corregido un error (la palabra clave try va antes de la lista de inicialización). Debería haberlo buscado en lugar de escribir desde la memoria, no es algo que se use con frecuencia :-) – puetzk

+76

Gracias por incluir la sintaxis try/catch para los inicializadores. He estado usando C++ durante 10 años, y esta es la primera vez que lo veo. – jakar

6
CDerived::CDerived() 
: CBase(...), iCount(0) //this is the initialisation list. You can initialise member variables here too. (e.g. iCount := 0) 
    { 
    //construct body 
    } 
40

En C++ no es un concepto de la lista de inicialización del constructor, que es donde se puede y debe llamar a la constructor de la clase base y en el que también debe inicializar los miembros de datos. La lista de inicialización viene después de la firma del constructor después de dos puntos y antes del cuerpo del constructor. Digamos que tenemos una clase A:


class A : public B 
{ 
public: 
    A(int a, int b, int c); 
private: 
    int b_, c_; 
}; 

Entonces, suponiendo que B tiene un constructor que toma un int, el constructor de A puede tener este aspecto:


A::A(int a, int b, int c) 
    : B(a), b_(b), c_(c) // initialization list 
{ 
    // do something 
} 

Como se puede ver, el constructor de la la clase base se llama en la lista de inicialización. Por cierto, la inicialización de los miembros de datos en la lista de inicialización es preferible a la asignación de los valores de b_, y c_ dentro del cuerpo del constructor, porque está ahorrando el costo adicional de la asignación.

Tenga en cuenta que los miembros de datos siempre se inicializan en el orden en que se declaran en la definición de clase, independientemente de su orden en la lista de inicialización. Para evitar errores extraños, que pueden surgir si sus miembros de datos dependen el uno del otro, siempre debe asegurarse de que el orden de los miembros sea el mismo en la lista de inicialización y la definición de la clase. Por la misma razón, el constructor de la clase base debe ser el primer elemento en la lista de inicialización. Si lo omite por completo, entonces se llamará automáticamente al constructor predeterminado para la clase base. En ese caso, si la clase base no tiene un constructor predeterminado, obtendrá un error de compilación.

+1

Espere un segundo ... Usted dice que los inicializadores ahorran en el costo de las asignaciones. ¿Pero no tienen lugar las mismas asignaciones dentro de ellos si se llaman? – levik

+5

No. Init y asignación son cosas diferentes. Cuando se llama a un constructor, intentará inicializar cada miembro de datos con lo que crea que es el valor predeterminado. En la lista de inicio, puede proporcionar valores predeterminados. Entonces incurre en costos de inicialización en cualquier caso. – Dima

+1

Y si usa la asignación dentro del cuerpo, incurrirá en el costo de inicialización de todos modos, y luego en el costo de la asignación además de eso. – Dima

11

Todo el mundo mencionó una llamada de constructor a través de una lista de inicialización, pero nadie dijo que el constructor de una clase padre puede ser llamado explícitamente desde el cuerpo del constructor del miembro derivado. Consulte la pregunta Calling a constructor of the base class from a subclass' constructor body, por ejemplo. El punto es que si utiliza una llamada explícita a una clase padre o constructor de superclase en el cuerpo de una clase derivada, esto simplemente está creando una instancia de la clase padre y no está invocando el constructor de la clase padre en el derivado objeto. La única forma de invocar una clase padre o un constructor de clase superior en un objeto de clase derivado es a través de la lista de inicialización y no en el cuerpo constructor de la clase derivada. Entonces tal vez no debería llamarse una "llamada de constructor de superclase". Puse esta respuesta aquí porque alguien podría confundirse (como yo lo hice).

+10

Esta respuesta es algo confusa a pesar de que la he leído un par de veces y eché un vistazo a la pregunta relacionada. Creo que lo que está diciendo es que si usas una llamada explícita a una clase padre o constructor de superclase en el cuerpo de una clase derivada, esto simplemente está creando una instancia de la clase padre y no está invocando la clase padre constructor en el objeto derivado. La única forma de invocar una clase padre o un constructor de clase superior en un objeto de clase derivado es a través de la lista de inicialización y no en el cuerpo constructor de la clase derivada. –

+0

@Richard Chambers Puede ser confuso ya que el inglés no es mi primer idioma, pero usted describió con precisión lo que traté de decir. –

1

Nadie mencionó la secuencia de llamadas al constructor cuando una clase se deriva de múltiples clases. La secuencia es como se mencionó al derivar las clases.

+2

Si nadie habló de ello, ¿dónde se mencionó? – EJP

+3

@EJP ya que la pregunta es sobre reglas de invocación, vale la pena mencionar la secuencia de llamadas en la respuesta –

7

Si tiene parámetros por defecto en su constructor base, se llamará automáticamente a la clase base.

using namespace std; 

class Base 
{ 
    public: 
    Base(int a=1) : _a(a) {} 

    protected: 
    int _a; 
}; 

class Derived : public Base 
{ 
    public: 
    Derived() {} 

    void printit() { cout << _a << endl; } 
}; 

int main() 
{ 
    Derived d; 
    d.printit(); 
    return 0; 
} 

de salida es: 1

+0

Esto es solo porque esa declaración particular crea un 'Base()' implícito, que tiene el mismo cuerpo que 'Base (int) 'pero más un inicializador implícito para': _a {1} '. Es 'Base()' que siempre se llama si no se encadena un constructor base específico en la lista de inicio. Y, como se mencionó en otra parte, los constructores de delegación de C++ 11 y la inicialización de corchete o igual hacen que los argumentos predeterminados sean menos necesarios (cuando ya estaban codiciados por el olor en muchos ejemplos). –

Cuestiones relacionadas