entre - Inicializando estructuras en C++
estructuras en c++ pdf (5)
8.5.1 / 12 "Agregados" dice:
Se consideran todas las conversiones de tipo implícitas (cláusula 4) al inicializar el miembro agregado con un inicializador desde una lista de inicializadores.
Asi que
A a = {0};
se inicializará con un char*
NULL char*
(como AndreyT y Johannes ), y
A a = {42};
fallará en el momento de la compilación ya que no hay una conversión implícita que coincida con un constructor std::string
.
Como una adición a esta pregunta , ¿qué está pasando aquí?
#include <string>
using namespace std;
struct A {
string s;
};
int main() {
A a = {0};
}
Obviamente, no puede establecer std :: string en cero. ¿Puede alguien proporcionar una explicación (respaldada con referencias al Estándar C ++, por favor) sobre lo que se supone que realmente suceda aquí? Y luego explica por ejemplo):
int main() {
A a = {42};
}
¿Alguno de estos está bien definido?
Una vez más, una pregunta embarazosa para mí: siempre doy mis constructores de estructuras, por lo que el problema nunca ha surgido antes.
Como han señalado las personas, esto "funciona" porque string tiene un constructor que puede tomar 0 como parámetro. Si decimos:
#include <map>
using namespace std;
struct A {
map <int,int> m;
};
int main() {
A a = {0};
}
entonces obtenemos un error de compilación, ya que la clase de mapa no tiene dicho constructor.
En 21.3.1 / 9, la norma prohíbe que el argumento char*
del constructor relevante de std::basic_string
sea un puntero nulo. Esto debería arrojar un std::logic_error
, pero todavía tengo que ver en qué parte del estándar está la garantía de que violar una precondición arroja un std::logic_error
.
Su estructura es un agregado , por lo que las reglas ordinarias para la inicialización agregada funcionan para él. El proceso se describe en 8.5.1. Básicamente todo el 8.5.1 está dedicado a él, así que no veo la razón para copiar todo aquí. La idea general es prácticamente la misma en C, solo se adaptó a C ++: se toma un inicializador de la derecha, se toma un miembro de la izquierda y se inicializa el miembro con ese inicializador. De acuerdo con 8.5 / 12, esto será una copia inicialización .
Cuando tu lo hagas
A a = { 0 };
básicamente está inicializando la copia as
con 0
, es decir, as
que es semánticamente equivalente a
string s = 0;
La anterior compila porque std::string
es convertible desde un puntero const char *
. (Y es un comportamiento indefinido, ya que el puntero nulo no es un argumento válido en este caso).
Su versión 42
no compilará por la misma razón que
string s = 42;
no compilará 42
no es una constante de puntero nulo, y std::string
no tiene medios para la conversión de tipo int
.
PD Por las dudas: tenga en cuenta que la definición de agregado en C ++ no es recursiva (a diferencia de la definición de POD, por ejemplo). std::string
no es un agregado, pero no cambia nada para su A
A
sigue siendo un agregado.
0 es una constante de puntero nulo
S.4.9:
Una constante de puntero nulo es una expresión de constante integral (5.19) rvalor de tipo entero que se evalúa como cero.
Una constante de puntero nulo se puede convertir a cualquier otro tipo de puntero:
S.4.9:
Una constante de puntero nulo se puede convertir a un tipo de puntero; el resultado es el valor del puntero nulo de ese tipo
Lo que dio para la definición de A
se considera un agregado:
S.8.5.1:
Un agregado es una matriz o una clase sin constructores declarados por el usuario, sin miembros de datos no estáticos protegidos o privados, sin clases base y sin funciones virtuales.
Está especificando una cláusula de inicialización:
S.8.5.1:
Cuando se inicializa un agregado, el inicializador puede contener una cláusula de inicialización que consiste en una lista de cláusulas de inicializador separadas por comas y separadas por comas para los miembros del agregado.
A
contiene un miembro del agregado de tipo std::string
y la cláusula de inicialización se aplica a él.
Su agregado está inicializado con copia
Cuando un agregado (ya sea clase o matriz) contiene miembros del tipo de clase y se inicializa mediante una lista de inicializadores incluida, cada uno de dichos miembros se inicializa con copia.
Copiar inicialización significa que tiene el equivalente a std::string s = 0
o std::string s = 42
;
S.8.5-12
La inicialización que ocurre en el paso de argumento, el retorno de función, el lanzamiento de una excepción (15.1), el manejo de una excepción (15.3) y las listas de inicializadores incluidos (8.5.1) se llama inicialización de copia y es equivalente a la forma T x = un;
std::string s = 42
no compilará porque no hay una conversión implícita, std::string s = 0
compilará (porque existe una conversión implícita) pero da como resultado un comportamiento indefinido.
El constructor de std::string
para const char*
no está definido como explicit
que significa que puede hacer esto: std::string s = 0
Solo para mostrar que las cosas se están inicializando con copia, puede hacer esta simple prueba:
class mystring
{
public:
explicit mystring(const char* p){}
};
struct A {
mystring s;
};
int main()
{
//Won''t compile because no implicit conversion exists from const char*
//But simply take off explicit above and everything compiles fine.
A a = {0};
return 0;
}