¿Cómo se inicializan los miembros de la clase C ++ si no lo hago explícitamente?

158

Supongamos que tengo una clase con memebers privadas ptr, name, pname, rname, crnamey age. ¿Qué sucede si no los inicializo yo mismo? Aquí hay un ejemplo:

class Example {
    private:
        int *ptr;
        string name;
        string *pname;
        string &rname;
        const string &crname;
        int age;

    public:
        Example() {}
};

Y luego hago:

int main() {
    Example ex;
}

¿Cómo se inicializan los miembros en ex? ¿Qué pasa con los punteros? Hacer stringy intobtener 0-inicializado con constructores predeterminados string()y int()? ¿Qué pasa con el miembro de referencia? ¿Y qué hay de las referencias constantes?

¿Qué más debo saber?

¿Alguien sabe un tutorial que cubra estos casos? Tal vez en algunos libros? Tengo acceso en la biblioteca de la universidad a muchos libros de C ++.

Me gustaría aprenderlo para poder escribir mejores programas (sin errores). Cualquier comentario ayudaría!

bodacydo
fuente
3
Para recomendaciones de libros, consulte stackoverflow.com/questions/388242/…
Mike Seymour el
Mike, ay, me refiero al capítulo de un libro que lo explica. ¡No todo el libro! :)
bodacydo
Sin embargo, probablemente sería una buena idea leer un libro completo en un idioma en el que pretendes programar. Y si ya leíste uno y no explicaba esto, entonces no era un libro muy bueno.
Tyler McHenry
2
Scott Meyers (un popular ex gurú de consejos de C ++) afirma en Effective C ++ , "las reglas son complicadas, demasiado complicadas para que valga la pena memorizarlas, en mi opinión ... asegúrese de que todos los constructores inicialicen todo en el objeto". Por lo tanto, en su opinión, la forma más fácil de (intentar) escribir un código "libre de errores" no es tratar de memorizar las reglas (y, de hecho, no las expone en el libro), sino inicializar todo explícitamente. Sin embargo, tenga en cuenta que incluso si adopta este enfoque en su propio código, podría trabajar en proyectos escritos por personas que no lo hacen, por lo que las reglas pueden ser valiosas.
Kyle Strand
2
@TylerMcHenry ¿Qué libros en C ++ considera "buenos"? He leído varios libros sobre C ++, pero ninguno de ellos lo ha explicado por completo. Como se señaló en mi comentario anterior, Scott Meyers se niega explícitamente a proporcionar las reglas completas en Effective C ++ . También he leído el C ++ moderno efectivo de Meyers , el Conocimiento común de C ++ de Dewhurst y Un recorrido por C ++ de Stroustrup . Para mi memoria, ninguno de ellos explicó las reglas completas. Obviamente podría haber leído el estándar, ¡pero difícilmente lo consideraría un "buen libro"! : D Y espero que Stroustrup probablemente lo explique en el lenguaje de programación C ++ .
Kyle Strand

Respuestas:

207

En lugar de una inicialización explícita, la inicialización de miembros en clases funciona de manera idéntica a la inicialización de variables locales en funciones.

Para los objetos , se llama a su constructor predeterminado. Por ejemplo, para std::string, el constructor predeterminado lo establece en una cadena vacía. Si la clase del objeto no tiene un constructor predeterminado, será un error de compilación si no lo inicializa explícitamente.

Para los tipos primitivos (punteros, enteros, etc.), que están no inicializados - contienen todo lo no deseado arbitraria pasó a estar en esa ubicación de memoria con anterioridad.

Para referencias (por ejemplo std::string&), es ilegal no inicializarlas, y su compilador se quejará y se negará a compilar dicho código. Las referencias siempre deben ser inicializadas.

Entonces, en su caso específico, si no se inicializan explícitamente:

    int *ptr;  // Contains junk
    string name;  // Empty string
    string *pname;  // Contains junk
    string &rname;  // Compile error
    const string &crname;  // Compile error
    int age;  // Contains junk
Tyler McHenry
fuente
44
+1. Vale la pena señalar que, según la estricta definición estándar, las instancias de tipos primitivos junto con una variedad de otras cosas (cualquier región de almacenamiento) se consideran objetos .
stinky472
77
"Si la clase del objeto no tiene un constructor predeterminado, será un error de compilación si no lo inicializa explícitamente" ¡eso está mal ! Si una clase no tiene un constructor predeterminado, se le da un constructor predeterminado predeterminado que está vacío.
Asistente
19
@wiz Creo que literalmente quiso decir 'si el objeto no tiene un constructor predeterminado' como tampoco en ninguno generado, lo que sería el caso si la clase define explícitamente cualquier constructor que no sea el predeterminado (no se generará un ctor predeterminado). Si nos volvemos demasiado pedantes, probablemente confundiremos más que ayuda y Tyler hace un buen punto sobre esto en su respuesta a mí antes.
stinky472
8
@ wiz-loz Yo diría que footiene un constructor, es implícito. Pero eso es realmente un argumento de semántica.
Tyler McHenry
44
Interpreto "constructor predeterminado" como un constructor al que se puede llamar sin argumentos. Esta sería una que usted mismo defina o generada implícitamente por el compilador. Por lo tanto, la falta de él no significa ni definido por usted mismo ni generado. O así es como lo veo.
suena el
28

Primero, déjame explicarte qué es una lista de inicializadores de memoria . Una mem-initializer-list es una lista separada por comas de mem-initializer s, donde cada mem-initializer es un nombre de miembro seguido de (, seguido de una lista de expresiones , seguida de a ). La lista de expresiones es cómo se construye el miembro. Por ejemplo, en

static const char s_str[] = "bodacydo";
class Example
{
private:
    int *ptr;
    string name;
    string *pname;
    string &rname;
    const string &crname;
    int age;

public:
    Example()
        : name(s_str, s_str + 8), rname(name), crname(name), age(-4)
    {
    }
};

la lista mem-initializer- del constructor proporcionado por el usuario, sin argumentos es name(s_str, s_str + 8), rname(name), crname(name), age(-4). Esta lista de inicialización de mem significa que el namemiembro se inicializa por el std::stringconstructor que toma dos iteradores de entrada , el rnamemiembro se inicializa con una referencia a name, el crnamemiembro se inicializa con una referencia constante namey el agemiembro se inicializa con el valor -4.

Cada constructor tiene su propia mem-initializer-list , y los miembros solo pueden inicializarse en un orden prescrito (básicamente el orden en que se declaran los miembros en la clase). Por lo tanto, los miembros de Examplesólo pueden ser inicializados en el orden: ptr, name, pname, rname, crname, y age.

Cuando no especifica un mem-initializer de un miembro, el estándar C ++ dice:

Si la entidad es un miembro de datos no estático ... de tipo de clase ..., la entidad se inicializa por defecto (8.5). ... De lo contrario, la entidad no se inicializa.

Aquí, debido a que namees un miembro de datos no estático de tipo de clase, se inicializa por defecto si no namese especificó ningún inicializador para en la lista de inicialización de mem . Todos los demás miembros de Exampleno tienen tipo de clase, por lo que no se inicializan.

Cuando el estándar dice que no están inicializados, esto significa que pueden tener cualquier valor. Por lo tanto, debido a que el código anterior no se inicializó pname, podría ser cualquier cosa.

Tenga en cuenta que aún debe seguir otras reglas, como la regla de que las referencias siempre deben inicializarse. Es un error del compilador no inicializar referencias.

Daniel Trebbien
fuente
Esta es la mejor manera de inicializar miembros cuando desea separar estrictamente la declaración (in .h) y la definición (in .cpp) sin mostrar demasiados elementos internos.
Matthieu
12

También puede inicializar miembros de datos en el momento en que los declara:

class another_example{
public:
    another_example();
    ~another_example();
private:
    int m_iInteger=10;
    double m_dDouble=10.765;
};

Utilizo este formulario casi exclusivamente, aunque he leído que algunas personas lo consideran 'mala forma', tal vez porque fue introducido recientemente, creo que en C ++ 11. Para mí es más lógico.

Otra faceta útil de las nuevas reglas es cómo inicializar los miembros de datos que son en sí mismos clases. Por ejemplo, suponga que CDynamicStringes una clase que encapsula el manejo de cadenas. Tiene un constructor que le permite especificar su valor inicial CDynamicString(wchat_t* pstrInitialString). Es muy posible que use esta clase como miembro de datos dentro de otra clase, digamos una clase que encapsula un valor de registro de Windows que en este caso almacena una dirección postal. Para 'codificar' el nombre de la clave de registro en la que escribe, usa llaves:

class Registry_Entry{
public:
    Registry_Entry();
    ~Registry_Entry();
    Commit();//Writes data to registry.
    Retrieve();//Reads data from registry;
private:
    CDynamicString m_cKeyName{L"Postal Address"};
    CDynamicString m_cAddress;
};

Tenga en cuenta que la segunda clase de cadena que contiene la dirección postal real no tiene un inicializador, por lo que se llamará a su constructor predeterminado en la creación, quizás configurándolo automáticamente en una cadena en blanco.


fuente
9

Si la clase de ejemplo se instancia en la pila, el contenido de los miembros escalares no inicializados es aleatorio e indefinido.

Para una instancia global, los miembros escalares no inicializados se pondrán a cero.

Para los miembros que son en sí mismos instancias de clases, se llamarán a sus constructores predeterminados, por lo que su objeto de cadena se inicializará.

  • int *ptr; // puntero no inicializado (o puesto a cero si es global)
  • string name; // constructor llamado, inicializado con una cadena vacía
  • string *pname; // puntero no inicializado (o puesto a cero si es global)
  • string &rname; // error de compilación si no puede inicializar esto
  • const string &crname; // error de compilación si no puede inicializar esto
  • int age; // valor escalar, no inicializado y aleatorio (o cero si es global)
Paul Dixon
fuente
Experimenté y parece que string nameestá vacío después de inicializar la clase en la pila. ¿Estás absolutamente seguro de tu respuesta?
bodacydo
1
string tendrá un constructor que proporcionó una cadena vacía de forma predeterminada - Aclararé mi respuesta
Paul Dixon
@bodacydo: Paul tiene razón, pero si te importa este comportamiento, nunca está de más ser explícito. Tíralo en la lista de inicializadores.
Stephen
Gracias por aclarar y explicar!
bodacydo
2
¡No es al azar! ¡Aleatorio es una palabra demasiado grande para eso! Si los miembros escalares fueran aleatorios, no necesitaríamos ningún otro generador de números aleatorios. Imagine un programa que analiza datos "sobrantes", como archivos recuperados en la memoria, los datos están lejos de ser aleatorios. ¡Ni siquiera está indefinido! Por lo general, es difícil de definir, porque generalmente no sabemos qué hace nuestra máquina. Si eso "datos aleatorios" que sólo se han borrado está la única imagen de su padre, tu madre puede incluso encontrar ofensivo si usted dice que su azar ...
slyy2048
5

Los miembros no estáticos no inicializados contendrán datos aleatorios. En realidad, solo tendrán el valor de la ubicación de memoria a la que están asignados.

Por supuesto, para los parámetros del objeto (como string) el constructor del objeto podría hacer una inicialización predeterminada.

En tu ejemplo:

int *ptr; // will point to a random memory location
string name; // empty string (due to string's default costructor)
string *pname; // will point to a random memory location
string &rname; // it would't compile
const string &crname; // it would't compile
int age; // random value
Mago
fuente
2

Los miembros con un constructor tendrán su constructor predeterminado llamado para la inicialización.

No puede depender del contenido de los otros tipos.

janm
fuente
0

Si está en la pila, el contenido de los miembros no inicializados que no tienen su propio constructor será aleatorio e indefinido. Incluso si es global, sería una mala idea confiar en que se eliminen a cero. Ya sea que esté en la pila o no, si un miembro tiene su propio constructor, se llamará para inicializarlo.

Entonces, si tiene string * pname, el puntero contendrá basura aleatoria. pero para el nombre de la cadena, se llamará al constructor predeterminado para la cadena, que le dará una cadena vacía. Para sus variables de tipo de referencia, no estoy seguro, pero probablemente será una referencia a algún fragmento de memoria aleatorio.

Jorge
fuente
0

Depende de cómo se construya la clase.

Al responder a esta pregunta se llega a comprender una gran declaración de caso de cambio en el estándar de lenguaje C ++, y una que es difícil de entender para los simples mortales.

Como un simple ejemplo de lo difícil que son las cosas:

main.cpp

#include <cassert>

int main() {
    struct C { int i; };

    // This syntax is called "default initialization"
    C a;
    // i undefined

    // This syntax is called "value initialization"
    C b{};
    assert(b.i == 0);
}

En la inicialización predeterminada, comenzaría desde: https://en.cppreference.com/w/cpp/language/default_initialization vamos a la parte "Los efectos de la inicialización predeterminada son" y comenzamos la declaración del caso:

  • "si T no es POD ": no (la definición de POD es en sí misma una gran declaración de cambio)
  • "si T es un tipo de matriz": no
  • "de lo contrario, no se hace nada": por lo tanto, se deja con un valor indefinido

Luego, si alguien decide inicializar el valor, vamos a https://en.cppreference.com/w/cpp/language/value_initialization "Los efectos de la inicialización del valor son" y comenzamos la declaración del caso:

  • "si T es un tipo de clase sin constructor predeterminado o con un constructor predeterminado proporcionado o eliminado por el usuario": no es el caso. Ahora pasará 20 minutos buscando en Google esos términos:
    • tenemos un constructor predeterminado implícitamente definido (en particular porque no se definió ningún otro constructor)
    • no es proporcionado por el usuario (definido implícitamente)
    • no se elimina ( = delete)
  • "si T es un tipo de clase con un constructor predeterminado que no es proporcionado por el usuario ni eliminado": sí
    • "el objeto se inicializa en cero y luego se inicializa por defecto si tiene un constructor predeterminado no trivial": no hay un constructor no trivial, solo se inicializa en cero. La definición de "inicialización cero" al menos es simple y hace lo que espera: https://en.cppreference.com/w/cpp/language/zero_initialization

Es por eso que le recomiendo que nunca confíe en la inicialización cero "implícita". A menos que haya fuertes razones de rendimiento, inicialice explícitamente todo, ya sea en el constructor si definió uno, o utilizando la inicialización agregada. De lo contrario, las cosas serán muy arriesgadas para futuros desarrolladores.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente