with initialize argument c++ c++11 initializer-list

c++ - initialize - ¿Cuándo usar el inicializador incluido?



c++ initializer list (3)

Creo que lo siguiente podría ser una buena guía:

  • Si el valor (único) con el que se está inicializando pretende ser el valor exacto del objeto, utilice la inicialización de copia ( = ) (porque en caso de error, nunca invocará accidentalmente un constructor explícito, que generalmente interpreta el valor proporcionado) valor de manera diferente). En lugares donde la inicialización de la copia no está disponible, vea si la inicialización del paréntesis tiene la semántica correcta, y si es así, use eso; de lo contrario, usa la inicialización de paréntesis (si eso tampoco está disponible, de todos modos no tienes suerte).

  • Si los valores con los que se está inicializando son una lista de valores que se almacenarán en el objeto (como los elementos de un vector / matriz o parte real / imaginaria de un número complejo), utilice la inicialización de llaves si está disponible.

  • Si los valores con los que se está inicializando no son valores que se almacenarán, sino que describen el valor / estado deseado del objeto, use paréntesis. Los ejemplos son el argumento de tamaño de un vector o el argumento de nombre de archivo de un fstream .

En C ++ 11, tenemos esa nueva sintaxis para inicializar las clases, lo que nos da un gran número de posibilidades para inicializar las 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 no era la intención de introducir las llaves.

Cuando se trata de un código de plantilla, cambiar la sintaxis puede llevar a diferentes significados, por lo que es esencial seguir el camino correcto.

Me pregunto si hay una guía universal que sintaxis uno debe elegir.


Estoy bastante seguro de que nunca habrá una guía universal. Mi enfoque es usar siempre llaves, recordando eso

  1. Los constructores de listas de inicializadores tienen prioridad sobre otros constructores
  2. Todos los contenedores de biblioteca estándar y std :: basic_string tienen constructores de listas de inicialización.
  3. La inicialización del corchete no permite reducir las conversiones.

Entonces las llaves redondas y rizadas no son intercambiables. Pero saber dónde difieren me permite utilizar la inicialización de corchete sobre la caja redonda en la mayoría de los casos (algunos de los casos en los que no puedo son actualmente errores de compilación).


Fuera del código genérico (es decir, plantillas), puede (y yo) utilizar llaves en todas partes . Una ventaja es que funciona en todas partes, por ejemplo, incluso para la inicialización en clase:

struct foo { // Ok std::string a = { "foo" }; // Also ok std::string b { "bar" }; // Not possible std::string c("qux"); // For completeness this is possible std::string d = "baz"; };

o para argumentos de funciones:

void foo(std::pair<int, double*>); foo({ 42, nullptr }); // Not possible with parentheses without spelling out the type: foo(std::pair<int, double*>(42, nullptr));

Para las variables, no presto mucha atención entre el T t = { init }; o T t { init }; estilos, creo que la diferencia es menor y, en el peor de los casos, solo resultará en un útil mensaje de compilación sobre el uso indebido de un constructor explicit .

Para los tipos que aceptan std::initializer_list aunque obviamente a veces se necesitan los constructores std::vector<int> twenty_answers(20, 42); std::initializer_list (el ejemplo clásico es std::vector<int> twenty_answers(20, 42); ). Está bien no usar aparatos ortopédicos entonces.

Cuando se trata de código genérico (es decir, en plantillas), el último párrafo debería haber provocado algunas advertencias. Considera lo siguiente:

template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T> { new T { std::forward<Args>(args)... } }; }

Luego auto p = make_unique<std::vector<T>>(20, T {}); crea un vector de tamaño 2 si T es eg int , o un vector de tamaño 20 si T es std::string . Una señal muy reveladora de que algo está sucediendo aquí es que no hay ningún rasgo que pueda salvarlo aquí (por ejemplo, con SFINAE): std::is_constructible está en términos de inicialización directa, mientras que usamos la inicialización de llaves que difiere a la inicialización directa si y solo si no hay un constructor que tome std::initializer_list interfiriendo. Del mismo modo std::is_convertible es de ayuda.

Investigué si, de hecho, es posible rodar a mano un rasgo que pueda solucionarlo, pero no soy demasiado optimista sobre eso. En cualquier caso, no creo que nos falte demasiado, creo que el hecho de que make_unique<T>(foo, bar) resulte en una construcción equivalente a T(foo, bar) es muy intuitivo; especialmente dado que make_unique<T>({ foo, bar }) es bastante diferente y solo tiene sentido si foo y bar tienen el mismo tipo.

Por lo tanto, para el código genérico, solo utilizo llaves para la inicialización del valor (p. Ej., T t {}; o T t = {}; ), lo cual es muy conveniente y creo que es superior al modo C ++ 03 T t = T(); . De lo contrario, es una sintaxis de inicialización directa (es decir, T t(a0, a1, a2); ) o, a veces, construcción predeterminada ( T t; stream >> t; siendo el único caso en el que uso eso, creo).

Eso no significa que todas las llaves sean malas, considere el ejemplo anterior con correcciones:

template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T> { new T(std::forward<Args>(args)...) }; }

Esto todavía usa llaves para construir std::unique_ptr<T> , aunque el tipo real dependa del parámetro de plantilla T