En C ++ 11, tenemos esa nueva sintaxis para inicializar clases que nos da una gran cantidad de posibilidades de cómo inicializar variables.
{ // Example 1
int b(1);
int a{1};
int c = 1;
int d = {1};
}
{ // Example 2
std::complex<double> b(3,4);
std::complex<double> a{3,4};
std::complex<double> c = {3,4};
auto d = std::complex<double>(3,4);
auto e = std::complex<double>{3,4};
}
{ // Example 3
std::string a(3,'x');
std::string b{3,'x'}; // oops
}
{ // Example 4
std::function<int(int,int)> a(std::plus<int>());
std::function<int(int,int)> b{std::plus<int>()};
}
{ // Example 5
std::unique_ptr<int> a(new int(5));
std::unique_ptr<int> b{new int(5)};
}
{ // Example 6
std::locale::global(std::locale("")); // copied from 22.4.8.3
std::locale::global(std::locale{""});
}
{ // Example 7
std::default_random_engine a {}; // Stroustrup's FAQ
std::default_random_engine b;
}
{ // Example 8
duration<long> a = 5; // Stroustrup's FAQ too
duration<long> b(5);
duration<long> c {5};
}
Para cada variable que declaro, tengo que pensar qué sintaxis de inicialización debo usar y esto ralentiza mi velocidad de codificación. Estoy seguro de que esa no era la intención de introducir las llaves.
Cuando se trata de código de plantilla, cambiar la sintaxis puede dar lugar a significados diferentes, por lo que es esencial ir por el camino correcto.
Me pregunto si existe una pauta universal sobre qué sintaxis se debe elegir.
c++
c++11
initializer-list
helami
fuente
fuente
Respuestas:
Creo que lo siguiente podría ser una buena guía:
Si el valor (único) con el que está inicializando está destinado a ser el valor exacto del objeto, use la
=
inicialización copy ( ) (porque entonces, en caso de error, nunca invocará accidentalmente un constructor explícito, que generalmente interpreta el valor proporcionado diferentemente). En lugares donde la inicialización de copia no está disponible, vea si la inicialización de llaves tiene la semántica correcta, y si es así, úsela; de lo contrario, use la inicialización de paréntesis (si eso tampoco está disponible, de todos modos no tendrá suerte).Si los valores con los que está inicializando son una lista de valores que se almacenarán en el objeto (como los elementos de un vector / matriz, o la parte real / imaginaria de un número complejo), use la inicialización con llaves si está disponible.
Si los valores con los que está inicializando no son valores para almacenar, pero describen el valor / estado previsto del objeto, utilice paréntesis. Algunos ejemplos son el argumento de tamaño de a
vector
o el argumento de nombre de archivo de unfstream
.fuente
T {}
o razones sintácticas como el análisis más molesto ), pero en general creo que este es un buen consejo. Tenga en cuenta que esta es mi opinión subjetiva, por lo que también debería echar un vistazo a las otras respuestas.type var{};
hace.Estoy bastante seguro de que nunca habrá una pauta universal. Mi enfoque es usar siempre llaves para recordar que
Entonces, los tirantes redondos y rizados no son intercambiables. Pero saber en qué se diferencian me permite usar la inicialización de corchetes rizados sobre redondos en la mayoría de los casos (algunos de los casos en los que no puedo son errores de compilación).
fuente
int i = 0;
No creo que nadie lo useint i{0}
allí, y podría ser confuso (también,0
es si el tipoint
, por lo que no habría un estrechamiento ). Para todo lo demás, seguiría el consejo de Juancho: prefiero {}, cuidado con los pocos casos en los que no debes. Tenga en cuenta que no hay muchos tipos que tomarán listas de inicializadores como argumentos de constructor, puede esperar que los contenedores y tipos similares a contenedores (tupla ...) los tengan, pero la mayoría del código llamará al constructor apropiado.int i{some floating point}
es un error, en lugar de truncarlo silenciosamente.{}
para decir "inicializar" a menos que no pueda en absoluto .Fuera del código genérico (es decir, plantillas), puede (y yo lo hago) usar llaves en todas partes . Una ventaja es que funciona en todas partes, por ejemplo, incluso para la inicialización en clase:
o para argumentos de función:
Para las variables a las que no presto mucha atención entre los estilos
T t = { init };
oT t { init };
, encuentro que la diferencia es menor y, en el peor de los casos, solo resultará en un mensaje útil del compilador sobre el mal uso de unexplicit
constructor.Para tipos que aceptan,
std::initializer_list
aunque obviamente, a vecesstd::initializer_list
se necesitan los no constructores (siendo el ejemplo clásicostd::vector<int> twenty_answers(20, 42);
). Entonces está bien no usar frenillos.Cuando se trata de código genérico (es decir, en plantillas), el último párrafo debería haber generado algunas advertencias. Considera lo siguiente:
Luego
auto p = make_unique<std::vector<T>>(20, T {});
crea un vector de tamaño 2 siT
esint
, por ejemplo , o un vector de tamaño 20 siT
esstd::string
. Una señal muy reveladora de que algo muy mal está sucediendo aquí es que no hay ningún rasgo que pueda salvarlo aquí (por ejemplo, con SFINAE):std::is_constructible
es en términos de inicialización directa, mientras que estamos usando inicialización de llaves, que difiere de la inicialización directa. inicialización si y solo si no hay ningún constructor questd::initializer_list
interfiera. Del mismo modo nostd::is_convertible
es de ayuda.He investigado si es posible aplicar un rasgo que pueda solucionarlo, pero no soy demasiado optimista al respecto. En cualquier caso no creo que nos falte mucho, creo que el hecho de que
make_unique<T>(foo, bar)
resulte en una construcción equivalente aT(foo, bar)
es muy intuitivo; especialmente dado quemake_unique<T>({ foo, bar })
es bastante diferente y solo tiene sentido sifoo
ybar
tienen el mismo tipo.Por lo tanto, para el código genérico, solo uso llaves para la inicialización de valores (por ejemplo,
T t {};
oT t = {};
), lo cual es muy conveniente y creo que es superior a la forma C ++ 03T t = T();
. De lo contrario, es una sintaxis de inicialización directa (es decirT t(a0, a1, a2);
) o, a veces, una construcción predeterminada (T t; stream >> t;
creo que es el único caso en el que lo uso).Sin embargo, eso no significa que todas las llaves sean malas, considere el ejemplo anterior con correcciones:
Esto todavía usa llaves para construir el
std::unique_ptr<T>
, aunque el tipo real depende del parámetro de la plantillaT
.fuente
make_unique<T>(20u, T {})
paraT
serunsigned
ostd::string
. No estoy muy seguro de los detalles. (Tenga en cuenta que también comenté sobre las expectativas con respecto a la inicialización directa frente a la inicialización de llaves con respecto, por ejemplo, a las funciones de reenvío perfecto).std::string c("qux");
No se ha especificado que funcione como una inicialización en clase para evitar ambigüedades con las declaraciones de funciones miembro en la gramática.