por - herencia c++ constructores
¿Por qué no compilará esto sin un constructor predeterminado? (3)
Puedo hacer esto:
#include <iostream>
int counter;
int main()
{
struct Boo
{
Boo(int num)
{
++counter;
if (rand() % num < 7) Boo(8);
}
};
Boo(8);
return 0;
}
Esto se compilará bien, mi resultado de contador es
21
.
Sin embargo, cuando intento crear el objeto
Boo
pasando el argumento del constructor en lugar de un literal entero, obtengo un error de compilación:
#include <iostream>
int counter;
int main()
{
struct Boo
{
Boo(int num)
{
++counter;
if (rand() % num < 7) Boo(num); // No default constructor
// exists for Boo
}
};
Boo(8);
return 0;
}
¿Cómo se llama a un constructor predeterminado en el segundo ejemplo pero no en el primer ejemplo? Este es el error que recibo en Visual Studio 2017.
En el compilador de C ++ en línea, en línea, recibo los errores:
error: no matching function for call to ‘main()::Boo::Boo()’
if (rand() % num < 7) Boo(num);
^
note: candidate expects 1 argument, 0 provided
Aquí está la advertencia del ruido
truct_init.cpp: 11: 11: error: redefinición de ''num'' con un tipo diferente: ''Boo'' vs ''int''
Clang da este mensaje de advertencia:
<source>:12:16: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named ''num'' [-Wvexing-parse]
Boo(num); // No default constructor
^~~~~
Este es un problema de análisis más desconcertante.
Porque
Boo
es el nombre de un tipo de clase y
num
no es un nombre de tipo,
Boo(num);
podría ser la construcción de un temporal de tipo
Boo
con
num
como argumento para el constructor de
Boo
o podría ser una declaración
Boo num;
con paréntesis adicionales alrededor del número del declarador (que los declaradores siempre pueden tener).
Si ambas son interpretaciones válidas, el estándar requiere que el compilador asuma una declaración.
Si se analiza como declaración, entonces
Boo num;
llamaría al constructor predeterminado (el constructor sin argumentos), que no está declarado por usted o implícitamente (porque usted declaró otro constructor).
Por lo tanto el programa está mal formado.
Esto no es un problema con
Boo(8);
, porque
8
no puede ser el identificador de una variable (declarator-id), por lo que se analiza como una llamada creando un
Boo
temporal con
8
como argumento al constructor, por lo que no llama al constructor predeterminado (que no está declarado), sino al que usted Definido manualmente.
Puede desambiguar esto de una declaración usando
Boo{num};
en lugar de
Boo(num);
(porque
{}
alrededor del declarador no está permitido), al hacer que la temporal sea una variable con nombre, por ejemplo,
Boo temp(num);
, o colocándolo como un operando en otra expresión, por ejemplo,
(Boo(num));
,
(void)Boo(num);
, etc.
Tenga en cuenta que la declaración estaría bien formada si el constructor predeterminado fuera utilizable, ya que está dentro del alcance del bloque de rama de
if
lugar del alcance del bloque de la función y simplemente ocultaría el
num
en la lista de parámetros de la función.
En cualquier caso, no parece una buena idea hacer un mal uso de la creación de objetos temporales para algo que debería ser una llamada de función normal (miembro).
Este tipo particular de análisis más desconcertante con un solo nombre no tipográfico entre paréntesis solo puede ocurrir porque la intención es crear un temporal y descartarlo inmediatamente o alternativamente si la intención es crear un temporal usado directamente como inicializador, por ejemplo
Boo boo(Boo(num));
(en realidad declara la función
boo
tomando un parámetro llamado
num
con tipo
Boo
y devolviendo
Boo
).
Por lo general, no se pretende descartar los temporales de manera inmediata y se puede evitar el caso del inicializador utilizando la inicialización de corsé o los paréntesis dobles (
Boo boo{Boo(num)}
,
Boo boo(Boo{num})
o
Boo boo((Boo(num)));
pero no
Boo boo(Boo((num)));
).
Si
Boo
no era un nombre de tipo, no podría ser una declaración y no se produce ningún problema.
También quiero enfatizar que
Boo(8);
está creando un nuevo temporal de tipo
Boo
, incluso dentro del alcance de la clase y la definición del constructor.
No es, como podría pensarse erróneamente, una llamada al constructor con el puntero de la persona que llama como para las funciones miembro no estáticas habituales.
No es posible llamar a otro constructor de esta manera dentro del cuerpo del constructor.
Esto solo es posible en la lista de inicialización de miembros del constructor.
Esto sucede a pesar de que la declaración estaría mal formada debido a la falta de un constructor, debido a [stmt.ambig]/3 :
La desambiguación es puramente sintáctica; es decir, el significado de los nombres que aparecen en dicha declaración, más allá de que sean nombres de tipo o no, generalmente no se utiliza ni se modifica por la desambiguación.
[...]
La desambiguación precede al análisis, y una declaración desambiguada como una declaración puede ser una declaración mal formada.
Se corrigió en la edición: pasé por alto que la declaración en cuestión estaba en un alcance diferente al del parámetro de función y, por lo tanto, la declaración estaba bien formada si el constructor estaba disponible. Esto no se considera durante la desambiguación en ningún caso. También se amplió en algunos detalles.
Esto se conoce como el análisis más molesto (el término fue utilizado por Scott Meyers en Effective STL) .
Boo(num)
no invoca al constructor ni crea un temporal.
Clang da una buena advertencia para ver (incluso con el nombre correcto W
vexing-parse
):
<source>:12:38: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named ''num'' [-Wvexing-parse]
Entonces, lo que el compilador ve es equivalente a
Boo num;
que es una decleración variable. Declaró una variable Boo con el nombre num, que necesita el constructor predeterminado, aunque deseaba crear un objeto Boo temporal. El estándar de C ++ requiere que el compilador en su caso asuma que esto es una declaración de variable. Ahora puedes decir: "Oye, num es un int, no hagas eso". Sin embargo, la norma dice :
La desambiguación es puramente sintáctica; es decir, el significado de los nombres que aparecen en dicha declaración, más allá de que sean nombres de tipo o no, generalmente no se utiliza ni se modifica por la desambiguación. Las plantillas de clase se crean instancias según sea necesario para determinar si un nombre calificado es un nombre de tipo. La desambiguación precede al análisis, y una declaración desambiguada como una declaración puede ser una declaración mal formada. Si, durante el análisis, un nombre en un parámetro de plantilla está enlazado de forma diferente a lo que estaría enlazado durante un análisis de prueba, el programa está mal formado. No se requiere ningún diagnóstico. [Nota: Esto puede ocurrir solo cuando el nombre se declara anteriormente en la declaración. - nota final]
Así que no hay forma de salir de esto.
Para
Boo(8)
esto no puede suceder, ya que el analizador puede estar seguro de que no es una declaración (8 no es un nombre de identificador válido) e invoca al constructor
Boo(int)
.
Por cierto: puede desambiguar utilizando paréntesis:
if (rand() % num < 7) (Boo(num));
o en mi opinión mejor, use la nueva sintaxis de inicialización uniforme
if (rand() % num < 7) Boo{num};