2009-05-11 15 views
19

Soy bastante nuevo en C++, y no estoy seguro de este. Eche un vistazo al siguiente ejemplo que resume mi problema actual.C++ - construcción de un objeto dentro de una clase

class Foo 
{ 
    //stuff 
}; 

class Bar 
{ 
    Foo foo; 
}; 

So Bar constata un objeto Foo completo, no solo una referencia o un puntero. ¿Este objeto está inicializado por su constructor predeterminado? ¿Debo llamar explícitamente su constructor y, de ser así, cómo y dónde?

Gracias.

+4

Estoy familiarizado con C++ y todavía tengo algunas dudas en este sentido cada cierto tiempo. Además, la mayoría de las respuestas indican claramente que se llamará al constructor predeterminado de Foo, y el hecho es que depende de la definición de Foo. ¿Es un constructor predeterminado proporcionado por el usuario o implícito? ¿Tiene algún atributo de miembro privado? La inicialización en C++ no es simple. –

+2

Bastante gracioso que @xtofl solicite al afiche que elimine el mensaje 'Estoy familiarizado con C++' ... Tal vez la mayoría de la gente no esté 'familiarizada' con C++ cuando casi todas las respuestas son incorrectas. De hecho, la inicialización es difícil, algunas de las personas que respondieron han demostrado su conocimiento de C++ @JaredPar, @dirkgently, @David Thornley y, sin embargo, han fallado. –

Respuesta

17

Se ha inicializado por su Constructor predeterminado. Si desea utilizar un constructor diferente, es posible que tenga algo como esto:

class Foo 
{ 
    public: 
    Foo(int val) { } 
    //stuff 
}; 

class Bar 
{ 
    public: 
    Bar() : foo(2) { } 

    Foo foo; 
}; 
+0

Error de sintaxis: necesita dos puntos (:) después de la palabra clave pública. – dirkgently

+2

Y un punto y coma (;) después del cierre} de cada clase. Yo culpo a Java. – richq

+0

gracias por las correcciones de sintaxis. Culpo a C#. –

1

So Bar constains a full Foo object, not just a reference or pointer. Is this object initialized by its default constructor?

Si Foo tiene una ctor defecto, un objeto de tipo Foo va a utilizar el ctor por defecto cuando se crea un objeto de tipo Bar. De lo contrario, debe llamar al Foo ctor usted mismo o su codificador Bar hará que su compilador se queje in situ.

Ej:

class Foo { 
public: 
Foo(double x) {} 
}; 

class Bar { 
Foo x; 
}; 

int main() { 
Bar b; 
} 

Lo anterior tendrá el compilador se queja algo como:

"In constructor 'Bar::Bar()': Line 5: error: no matching function for call to 'Foo::Foo()'

Do I need to explicitly call its constructor, and if so, how and where ?

Modificar el ejemplo anterior de la siguiente manera:

class Foo { 
public: 
    Foo(double x) {} // non-trivial ctor 
}; 

class Bar {  
Foo x; 
public: 
    Bar() : x(42.0) {} // non-default ctor, so public access specifier required 
}; 

int main() { 
Bar b; 
} 
+0

Si Foo no tiene ningún constructor definido (además de posiblemente el constructor de copia), entonces el compilador no se quejará en absoluto pero tampoco llamará al constructor Foo implícitamente definido. El objeto será sin inicializar. –

+0

Eso es lo que quise decir por defecto ctor. – dirkgently

+0

¿Es posible inicializar el objeto sin usar listas de inicialización? Como en, ¿puedes inicializar el objeto dentro de {} del constructor? –

2

Si usted no llama explícitamente un constructor de foo interior del constructor del bar, el que viene por defecto será utilizado. Esto se puede controlar mediante una llamada explícita al constructor

Bar::Bar() : foo(42) {} 

Esto es, por supuesto, asumiendo que se agrega una Foo :: foo (int) para el código :)

+1

Veo que a la mayoría de ustedes se les ocurrió "42" en sus ejemplos. De donde viene eso? – ichiban

+7

Esa es una referencia al H2G2: la respuesta a la última cuestión de la vida, el universo y todo. Lea: http://en.wikipedia.org/wiki/42_(number)#In_The_Hitchhiker.27s_Guide_to_the_Galaxy. Mejor aún, lee a Douglas Adams. – dirkgently

+0

@ichiban, @dirkgently es correcto. – JaredPar

1

objeto completo. No, está construido por defecto en el constructor predeterminado de Bar.

Ahora, si Foo tuviera un constructor que solo tomara, digamos, un int. Se necesitaría un constructor en Bar llamar al constructor de Foo, y decir lo que es:

class Foo { 
public: 
    Foo(int x) { .... } 
}; 

class Bar { 
public: 
    Bar() : foo(42) {} 

    Foo foo; 
}; 

Pero si Foo tenía un constructor por defecto Foo(), el compilador genera el constructor del bar de forma automática, y que llamarían por defecto de Foo (es decir, Foo())

+0

Veo que a la mayoría de ustedes se les ocurrió "42" en sus ejemplos. De donde viene eso? – ichiban

+0

Sin trabajo, todos los ctors son privados por defecto. – dirkgently

+0

¡Vaya! Fijo. Tiendo a dejar eso fuera cuando pienso/habla sobre otros conceptos. – Macke

1

A menos que especifique lo contrario, foo se inicializa utilizando su constructor predeterminado. Si desea utilizar algún otro constructor, es necesario hacerlo en la lista de inicialización para Bar:

Bar::Bar(int baz) : foo(baz) 
{ 
    // Rest of the code for Bar::Bar(int) goes here... 
} 
+0

Si Foo tiene un constructor predeterminado definido por el usuario, se ejecutará. Para un constructor predeterminado implícitamente definido en Foo, no se realizará ninguna llamada en el constructor de Bar implícitamente definido. –

+0

¿Es posible inicializar el objeto sin usar listas de inicialización? Como en, ¿puedes inicializar el objeto dentro de {} del constructor? –

+0

@JustinLiang, no, una vez que se ejecuta el cuerpo del constructor, se han inicializado todos los objetos que son parte de la clase que se inicializará. Si el objeto no tiene un constructor predeterminado, se requiere llamar al constructor en la lista de inicializadores. Sin embargo, puede usar la asignación para cambiar objetos y datos en la clase, pero tenga en cuenta que esto es menos eficiente ya que el objeto se inicializa y luego se sobrescribe con la asignación. – Naaff

0

No es necesario llamar al contructor por defecto de forma explícita en C++, que se llamará para usted. Si usted quiere llamar a un contructor diferente, usted puede hacer esto:

Foo foo(somearg) 
+0

Si Foo tiene un constructor predeterminado definido por el usuario, se ejecutará. Para un constructor predeterminado definido implícitamente en Foo, no se realizará ninguna llamada en el constructor implícitamente definido de la barra –

4

Hay cuatro funciones compilador de C++ generará para cada clase, si se puede, y si no les proporcionan: un constructor por defecto , un constructor de copia, un operador de asignación y un destructor.En el Estándar de C++ (capítulo 12, "Funciones especiales"), estos se conocen como "declarados implícitamente" e "implícitamente definidos". Tendrán acceso público.

No confunda "implícitamente definido" con "predeterminado" en un constructor. El constructor predeterminado es el que se puede llamar sin ningún argumento, si hay uno. Si no proporciona ningún constructor, se definirá implícitamente uno predeterminado. Utilizará los constructores por defecto para cada clase base y miembro de datos.

Entonces, lo que sucede es que la clase Foo tiene un constructor predeterminado definido implícitamente, y Bar (que no parece tener un constructor definido por el usuario) usa su constructor predeterminado definido implícitamente que llama al constructor predeterminado de Foo.

Si desea escribir un constructor para Bar, puede mencionar a foo en su lista de inicializadores, pero como está utilizando el constructor predeterminado, no tiene que especificarlo.

Recuerde que, si escribe un constructor para Foo, el compilador no generará automáticamente un constructor predeterminado, por lo que tendrá que especificar uno si necesita uno. Por lo tanto, si pusiera algo como Foo(int n); en la definición de Foo y no escribiera explícitamente un constructor predeterminado (Foo(); o), no podría tener una barra en su forma actual, ya que no podría usar Constructor por defecto de Foo. En este caso, tendrías que tener un constructor como Bar(int n = 0): foo(n) {} que tenga el constructor de Bar para inicializar el Foo. (Tenga en cuenta que Bar(int n = 0) {foo = n;} o similar no funcionaría, ya que el constructor de barras primero intentaría inicializar foo, y eso fallaría.)

+0

. El constructor definido implícitamente en la barra NO llamará a un constructor implícitamente definido en Foo. Si Foo tiene un constructor definido por el usuario, Bar constructor implícitamente definido (esta vez sí) llamará al constructor definido por el usuario en Foo. Esta es la respuesta más completa hasta ahora, no obstante: +1 –

+0

¿Seguro de eso? Según 12.6.2 (4), un miembro de datos no estático se inicializa con su constructor predeterminado si no se menciona en la lista de inicializadores, y en 8.5 (5) se llama al constructor predeterminado a menos que sea un POD (datos antiguos simples, no convenientemente definidos en el estándar), en cuyo caso se inicializa a cero. Entonces, supongo que la pregunta es si Foo es un tipo POD, que no podemos ver. Si es tipo POD, todo se inicializa en cero; si tiene una de las cosas que lo marcan como no POD, se inicializa por defecto y, por lo tanto, llama al constructor predeterminado implícitamente definido. –

12

La construcción es un tema bastante difícil en C++. La respuesta simple es depende. Si Foo se inicializa o no depende de la definición de Foo. Sobre la segunda pregunta: cómo hacer que Bar inicialice Foo: listas de inicialización son la respuesta.

Aunque el consenso general es que Foo se inicializará por defecto mediante el constructor predeterminado implícito (compilador generado), no es necesario que sea verdadero.

Si Foo no tiene un constructor predeterminado definido por el usuario, entonces Foo no se inicializará. Para ser más precisos: cada miembro de la barra o del Foo carecen de una definida por el usuario constructor por defecto será inicializada por el compilador genera constructor por defecto de la barra:

class Foo { 
    int x; 
public: 
    void dump() { std::cout << x << std::endl; } 
    void set() { x = 5; } 
}; 
class Bar { 
    Foo x; 
public: 
    void dump() { x.dump(); } 
    void set() { x.set(); } 
}; 
class Bar2 
{ 
    Foo x; 
public: 
    Bar2() : Foo() {} 
    void dump() { x.dump(); } 
    void set() { x.set(); } 
}; 
template <typename T> 
void test_internal() { 
    T x; 
    x.dump(); 
    x.set(); 
    x.dump(); 
} 
template <typename T> 
void test() { 
    test_internal<T>(); 
    test_internal<T>(); 
} 
int main() 
{ 
    test<Foo>(); // prints ??, 5, 5, 5, where ?? is a random number, possibly 0 
    test<Bar>(); // prints ??, 5, 5, 5 
    test<Bar2>(); // prints 0, 5, 0, 5 
} 

Ahora, si Foo tenía un constructor definido por el usuario y luego lo haría inicializarse siempre, independientemente de si Bar tiene o no un constructor inicializado por el usuario. Si Bar tiene un constructor definido por el usuario que explícitamente llama al constructor (posiblemente implícitamente definido) de Foo, entonces Foo se inicializará. Si la lista de inicialización de Bar no llama al constructor Foo, entonces será equivalente al caso en que Bar no tenga un constructor definido por el usuario.

El código de la prueba puede necesitar algunas explicaciones. Estamos interesados ​​en si el compilador inicializa la variable sin que el código de usuario realmente llame al constructor. Queremos probar si el objeto está inicializado o no. Ahora bien, si simplemente creamos un objeto en una función, es posible que golpee una posición de memoria que no se haya tocado y que ya contenga ceros. Queremos diferenciar la suerte del éxito, por lo que definimos una variable en una función y llamamos a la función dos veces. En la primera ejecución, imprimirá los contenidos de la memoria y forzará un cambio. En la segunda llamada a la función, dado que el seguimiento de la pila es el mismo, la variable se mantendrá exactamente en la misma posición de memoria. Si se inicializó, se establecería en 0, de lo contrario mantendría el mismo valor que tenía la variable anterior en exactamente la misma posición.

En cada una de las ejecuciones de prueba, el primer valor impreso es el valor inicializado (si realmente se inicializó) o el valor en esa posición de memoria, que en algunos casos es 0. El segundo valor es solo una prueba token que representa el valor en la posición de memoria después de cambiarlo manualmente. El tercer valor proviene de la segunda ejecución de la función. Si la variable se está inicializando, retrocederá a 0. Si el objeto no se inicializa, su memoria mantendrá los contenidos anteriores.

+0

¿Sabe por qué cuando ejecuta la prueba () la salida es ??, 5, 5, 5 en lugar de ??, 5, ??, 5 no sale el objeto Foo (x) cuando test_internal regresa ? Del mismo modo para la prueba ()? – user1084113

+0

@ user1084113: Debido a que es un comportamiento indefinido, puede obtener cualquiera de los dos, pero la prueba abusa del conocimiento de cómo los compiladores/arquitecturas comúnmente usan la pila. Básicamente, cuando se ingresa una función, toma una parte de la pila para sus variables internas que luego se libera cuando la función finaliza (dependiendo de la convención de llamadas puede ser la persona que llama o la función que actualiza el puntero de la pila). La segunda llamada a la función utiliza el mismo bloque de memoria para las variables locales, establecidas en el mismo orden. La 'x' no está inicializada y tiene el valor asignado en la última ejecución de la función. –

Cuestiones relacionadas