Supongamos que tengo una clase con memebers privadas ptr
, name
, pname
, rname
, crname
y 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 string
y int
obtener 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!
c++
initialization
member-initialization
bodacydo
fuente
fuente
Respuestas:
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:
fuente
foo
tiene un constructor, es implícito. Pero eso es realmente un argumento de semántica.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, enla 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 elname
miembro se inicializa por elstd::string
constructor que toma dos iteradores de entrada , elrname
miembro se inicializa con una referencia aname
, elcrname
miembro se inicializa con una referencia constantename
y elage
miembro 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
Example
sólo pueden ser inicializados en el orden:ptr
,name
,pname
,rname
,crname
, yage
.Cuando no especifica un mem-initializer de un miembro, el estándar C ++ dice:
Aquí, debido a que
name
es un miembro de datos no estático de tipo de clase, se inicializa por defecto si noname
se especificó ningún inicializador para en la lista de inicialización de mem . Todos los demás miembros deExample
no 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.
fuente
.h
) y la definición (in.cpp
) sin mostrar demasiados elementos internos.También puede inicializar miembros de datos en el momento en que los declara:
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
CDynamicString
es una clase que encapsula el manejo de cadenas. Tiene un constructor que le permite especificar su valor inicialCDynamicString(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: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
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íastring *pname;
// puntero no inicializado (o puesto a cero si es global)string &rname;
// error de compilación si no puede inicializar estoconst string &crname;
// error de compilación si no puede inicializar estoint age;
// valor escalar, no inicializado y aleatorio (o cero si es global)fuente
string name
está vacío después de inicializar la clase en la pila. ¿Estás absolutamente seguro de tu respuesta?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:
fuente
Los miembros con un constructor tendrán su constructor predeterminado llamado para la inicialización.
No puede depender del contenido de los otros tipos.
fuente
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.
fuente
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
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:
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:
= delete
)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.
fuente