2010-05-14 19 views
28

Como una adición al this question, lo que está pasando aquí:estructuras Inicialización en C++

#include <string> 
using namespace std; 

struct A { 
    string s; 
}; 

int main() { 
    A a = {0}; 
} 

Obviamente, no se puede establecer un std :: string a 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 explique por ejemplo):

int main() { 
    A a = {42}; 
} 

¿Están bien definidos estos?

Una vez más, una pregunta embarazosa para mí, siempre doy mis constructores de estructuras, por lo que el problema nunca ha surgido antes.

+1

La plantilla de clase 'boost :: array' es un agregado también. Entonces puede hacer 'array a = {" foo "," bar "};' con él, por ejemplo. Además, mi lazy-construct-array es un agregado también: http://stackoverflow.com/questions/2662417/c-suppress-automatic-initialization-and-destruction/2662526#2662526 –

+3

Conversiones implícitas + agregados ... ಠ_ಠ –

+0

@litb cuando vi por primera vez esa función de 'boost :: array' tuve una iluminación, también conocida como satisfacción sexual del cerebro. Las cosas simples que tienen tanto sentido tienden a hacerme eso. – wilhelmtell

Respuesta

29

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, esta será una copia-inicialización .

Al hacer

A a = { 0 }; 

que son básicamente la copia-inicializar a.s con 0, es decir, para a.s es semánticamente equivalente a

string s = 0; 

Las compilaciones anteriores porque std::string es convertible de un puntero const char * . (Y es un comportamiento indefinido, puesto puntero nulo no es un argumento válido en este caso.)

Su versión 42 no se compilará por la misma razón, el

string s = 42; 

no se compilará. 42 no es una constante de puntero nulo, y std::string no tiene medios para la conversión de tipo int.

P.S. 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

§12.6.1 también es relevante, como se señala en § 8.5.1 13. – outis

+0

@outis: miré a través de 12.6.1 y no pude ver de inmediato lo que se agregó a lo que ya estaba en 8.5. Cada vez que 12.6.1 trata con la inicialización agregada, parece referirse a 8.5 :) – AnT

+0

Es interesante ver que en 'basic_string (size_type n, charT c, const Allocator a = Allocator())' hay una razón por la cual 'size_type n' no tiene un valor predeterminado. La razón es que es una mala idea sobrecargar un puntero y un número entero. El valor 0 (cero) es estrictamente un número entero, no un puntero, por lo que no sería posible construir a través de la sobrecarga del puntero con un puntero nulo, a menos, por supuesto, que se expulse explícitamente.El estándar evita esta confusión al requerir un tipo de carácter si especifica una longitud de cadena en la construcción de la cadena. – wilhelmtell

8

8.5.1/12 "agregados" dice:

Todas las conversiones de tipo implícitas (cláusula 4) se consideran cuando se inicializa el miembro agregado con un inicializador desde un inicializador-list.

Así

A a = {0}; 

obtendrá inicializado con un nulo char* (como AndreyT y Johannes indicado), y

A a = {42}; 

se producirá un error en tiempo de compilación ya que no hay conversión implícita que va hacer coincidir con un constructor std::string.

2

Como han señalado las personas, esto "funciona" porque la cadena 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.

+0

¿Por qué tu avatar de foto no se muestra en esta respuesta? ¿Está escondido en las respuestas de la comunidad? –

+0

@Johannes ¡Un misterio! ¿Desea informarlo como un error en meta o debo? –

+0

@Neil go por ello :) –

1

En 21.3.1/9, la norma prohíbe que el argumento char* del constructor pertinente de std::basic_string sea un puntero nulo. Esto debería arrojar un std::logic_error, pero todavía tengo que ver dónde en el estándar está la garantía de que violar una condición previa arroja un std::logic_error.

+0

Si no me equivoco, la violación de una precondición garantiza un comportamiento indefinido, no una excepción. –

+1

@James g ++ 4.0.1 en OS X 10.5.8 arroja un 'std :: logic_error' en tiempo de construcción. 19.1.1 dice que esto es para lo que 'logic_error' es, pero no puedo encontrar una garantía de que esto es lo que sucede cuando hay una violación de un invariante o precondición. – wilhelmtell

3

0 es una constante

S.4.9 puntero nulo:

Una constante puntero nulo es una expresión de la constante integral (5.19) rvalue de tipo entero que se evalúa como cero.

Una constante puntero nulo se puede convertir en cualquier otro tipo de puntero:

S.4.9:

Una constante puntero nulo se puede convertir en un tipo de puntero; el resultado es el valor de puntero nulo de ese tipo

Lo que diste 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, no hay miembros de datos no estáticos privados o protegidos, no hay clases base y no hay funciones virtuales.

especifica una cláusula de inicialización:

S.8.5.1:

Cuando un agregado se inicializa el inicializador puede contener una cláusula inicializador que consiste en una abrazadera cerrada, lista separada por comas de inicializador-cláusulas 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 es copia-inicializado

Cuando un agregado (si clase o matriz) contiene los miembros de tipo de clase y se inicializa por un inicializador-lista abrazadera cerrada, cada uno de tales miembros es copia -inicializado

ejemplar significa que usted tiene el equivalente a std::string s = 0 o std::string s = 42 inicialización;

S.8.5-12

La inicialización que se produce en el paso de argumentos, retorno de la función, lanzar una excepción (15.1), la manipulación una excepción (15.3), y las listas de inicializador abrazadera-cerrado (8,5 .1) se llama inicialización de copia y es equivalente a con la forma T x = a;

std::string s = 42 no se compilará porque no hay conversión implícita, std::string s = 0 compilará (porque existe una conversión implícita), pero los resultados en el comportamiento no definido. constructor de

std::string 's para const char* no se define como explicit que significa que puede hacer esto: std::string s = 0

Sólo para mostrar que las cosas son realmente estar copia-inicializado, se podría hacer esta sencilla 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; 
}