Pregunta

digamos que tengo     std::map< std::string, std::string > m_someMap como una variable miembro privada de clase A

Dos preguntas: (y la única razón por la que pregunto es porque me encontré con un código así)

  1. ¿Cuál es el propósito de esta línea?

    A::A() : m_someMap()
    

    Ahora sé que esto es inicialización, pero ¿tienes que hacer esto así? Estoy confundido.

  2. ¿Cuál es el valor predeterminado de <=>, también C # define que int, double, etc. siempre se inicializa en defualt 0 y los objetos son nulos (al menos en la mayoría de los casos) Entonces, ¿cuál es la regla en C ++? ¿los objetos se inicializan por defualt a nulo y primitivos a basura? Por supuesto, me estoy ocupando de las variables de instancia.

EDITAR:

también, dado que la mayoría de las personas señalaron que esta es una opción de estilo y no necesaria, ¿qué pasa con:

A :: A (): m_someMap (), m_someint (0), m_somebool (falso)

¿Fue útil?

Solución

m_somemap

  1. No tienes que hacerlo.
  2. Lo que obtienes si lo omites: un std::map< std::string, std::string > vacío, es decir, una instancia válida de ese mapa que no tiene elementos.

m_somebool

  1. Debe inicializarlo a true o false si desea que tenga un valor conocido. Los booleanos son & Quot; tipos de datos antiguos simples & Quot; y no tienen el concepto de constructor. Además, el lenguaje C ++ no especifica valores predeterminados para booleanos no inicializados explícitamente.
  2. Lo que obtienes si lo omites: un miembro booleano con un valor no especificado. No debe hacer esto y luego usar su valor. Debido a eso, es una política muy recomendable que inicialice todos los valores de este tipo.

m_someint

  1. Debe inicializarlo a algún valor entero si desea que tenga un valor conocido. Los enteros son & Quot; tipos de datos antiguos simples & Quot; y no tienen el concepto de constructor. Además, el lenguaje C ++ no especifica valores predeterminados para enteros no inicializados explícitamente.
  2. Lo que obtienes si lo omites: un miembro int con un valor no especificado. No debe hacer esto y luego usar su valor. Debido a eso, es una política muy recomendable que inicialice todos los valores de este tipo.

Otros consejos

No hay necesidad de hacerlo realmente.
El constructor predeterminado lo hará automáticamente.

Pero a veces, al hacerlo explícito, actúa como una especie de documentación:

class X
{
    std::map<string,string>  data;
    Y                        somePropertyOfdata;

    X()
      :data()                    // Technically not needed
      ,somePropertyOfdata(data)  // But it documents that data is finished construction
    {}                           // before it is used here.
};

La regla en C ++ es que, a menos que inicialice explícitamente los datos de POD, no está definida, mientras que otras clases tienen un constructor predeterminado llamado automáticamente (incluso si el programador no lo hace explícitamente).

Pero diciendo eso. Considera esto:

template<typename T>
class Z
{
     T  data;   
     Z()
        :data()    // Technicall not need as default constructor will
                   // always be called for classes.
                   // But doing this will initialize POD data correctly
                   // if T is a basic POD type. 
     {}
};

Aquí esperaría que los datos se inicialicen por defecto.
Técnicamente, el POD no tiene constructores, por lo que si T fuera int, ¿esperarías que hiciera algo? Debido a que se inicializó explícitamente, está establecido en 0 o el equivalente para los tipos de POD.

Para la edición:

class A
{
    std::map<string,string>   m_someMap;
    int                       m_someint;
    bool                      m_somebool;
   public:
    A::A()
       : m_someMap()      // Class will always be initialised (so optional)
       , m_someint(0)     // without this POD will be undefined
       , m_somebool(false)// without this POD will be undefined
    {}
};

Como otros señalaron: no es necesario, sino más o menos una cuestión de estilo. La ventaja: muestra que desea explícitamente usar el constructor predeterminado y hace que su código sea más detallado. La desventaja: si tiene más de un ctor, puede ser difícil mantener los cambios en todos ellos y, a veces, agrega miembros de la clase y olvida agregarlos a la lista de inicializadores de ctors y hacer que parezca inconsistente.

A::A() : m_someMap()

Esta línea es innecesaria en este caso. Sin embargo, en general , es la única forma adecuada de inicializar a los miembros de la clase.

Si tiene un constructor como este:

X() : y(z) {
 w = 42;
}

entonces sucede lo siguiente cuando se llama al constructor X:

  • Primero, todos los miembros se inicializan: para y, decimos explícitamente que deseamos llamar al constructor que toma un z como argumento. Para w, lo que sucede depende del tipo de :. Si m_someMap es un tipo de POD (es decir, básicamente un tipo compatible con C: sin herencia, sin constructores ni destructores, todos los miembros públicos, y todos los miembros son también tipos de POD), entonces es no inicializado. Su valor inicial es cualquier basura encontrada en esa dirección de memoria. Si : m_someMap() es un tipo que no es POD, se llama a su constructor predeterminado (los tipos que no son POD se inicializan siempre en la construcción).
  • Una vez que ambos miembros han sido construidos, luego llamamos al operador de asignación para asignar 42 a <=>.

Lo importante a tener en cuenta es que todos los constructores se llaman antes de que ingresemos al cuerpo del constructor. Una vez que estamos en el cuerpo, todos los miembros ya han sido inicializados. Entonces, hay dos posibles problemas con nuestro cuerpo de constructor.

  • ¿Qué sucede si <=> es de un tipo que no tiene un constructor predeterminado? Entonces esto no se compilará. Luego, debe inicializarse explícitamente después de <=>, como <=> es.
  • ¿Qué pasa si esta secuencia de llamadas a ambos constructor predeterminado y operador de asignación es innecesariamente lenta? Tal vez sería mucho más eficiente simplemente llamar al constructor correcto para empezar.

Entonces, en resumen, dado que <=> es un tipo que no es POD, estrictamente hablando no tenemos que hacer <=>. Hubiera sido construido por defecto de todos modos. Pero si hubiera sido un tipo de POD, o si hubiéramos querido llamar a otro constructor que no fuera el predeterminado, entonces habríamos tenido que hacer esto.

Solo para tener claro lo que está sucediendo (con respecto a su segunda pregunta)

std::map< std::string, std::string > m_someMap crea una variable de pila llamada m_someMap y se llama al constructor predeterminado. La regla para C ++ para todos sus objetos es si va:

T varName;

donde T es un tipo, varName se construye por defecto.

T* varName;

debe asignarse explícitamente a NULL (o 0) - o nullptr en el nuevo estándar.

Para aclarar el problema del valor predeterminado:

C ++ no tiene el concepto de que algunos tipos implícitamente sean una referencia. A menos que algo se declare explícitamente como un puntero, no puede tomar un valor nulo. Esto significa que cada clase tendrá un constructor predeterminado para construir el valor inicial cuando no se especifiquen parámetros de constructor. Si no se declara ningún constructor predeterminado, el compilador generará uno para usted. Además, cada vez que una clase contiene miembros que son de tipos clasificados, esos miembros se inicializarán implícitamente a través de sus propios constructores predeterminados en la construcción del objeto, a menos que use la sintaxis de dos puntos para llamar explícitamente a un constructor diferente.

Sucede que el constructor predeterminado para todos los tipos de contenedores STL crea un contenedor vacío. Otras clases pueden tener otras convenciones para lo que hacen sus constructores predeterminados, por lo que aún debe saber que se invocan en situaciones como esta. Es por eso que la línea A::A() : m_someMap(), que realmente solo le dice al compilador que haga lo que ya haría de todos modos.

Cuando crea un objeto en C ++, el constructor sigue la siguiente secuencia:

  1. Llame a los constructores de todas las clases virtuales principales en todo el árbol de clases (en un orden arbitrario)
  2. Llame a los constructores de todas las clases primarias directamente heredadas en el orden de la declaración
  3. Llame a los constructores de todas las variables miembro en el orden de la declaración

Hay algunos detalles más que esto, y algunos compiladores le permiten forzar algunas cosas fuera de este orden específico, pero esta es la idea general. Para cada una de estas llamadas de constructor, puede especificar los argumentos del constructor, en cuyo caso C ++ llamará al constructor como se especifica, o puede dejarlo solo y C ++ intentará llamar al constructor predeterminado. El constructor predeterminado es simplemente el que no toma argumentos.

Si alguna de sus clases primarias virtuales, clases primarias no virtuales o variables miembro no tienen un constructor predeterminado o necesitan crearse con algo distinto al predeterminado, los agrega a la lista de llamadas de constructor. Debido a que C ++ asume una llamada de constructor por defecto, no hay absolutamente ninguna diferencia entre poner un constructor por defecto en la lista y dejarlo por completo (C ++ no creará (a menos que en circunstancias especiales fuera del alcance de esta pregunta) un objeto sin una llamada a un constructor de algún tipo). Si una clase no tiene un constructor predeterminado y no proporciona una llamada de constructor, el compilador arrojará un error.

Cuando se trata de tipos integrados como float o int, el constructor predeterminado no hace nada, por lo que la variable tendrá el valor predeterminado de lo que quede en la memoria. Todos los tipos incorporados también tienen un constructor de copia, por lo que puede inicializarlos pasando su valor inicial como el único argumento para el constructor de la variable.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top