¿Cuándo usar el inicializador corsé?


En C++11, tenemos esa nueva sintaxis para inicializar clases que nos da un gran número 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 los soportes rizados.

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

I me pregunto si hay una guía universal que sintaxis uno debería elegir.

Author: helami, 2012-04-02

3 answers

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 de manera diferente). En los 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í, use eso; de lo contrario, use inicialización de paréntesis (si eso tampoco está disponible, no tiene suerte de todos modos).

  • 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/array, o parte real/imaginaria de un número complejo), use inicialización de llaves si está disponible.

  • Si los valores están inicializando con no los valores a ser almacenados, pero describir la intención de valor / estado del objeto, use paréntesis. Ejemplos son el argumento size de un vector o el argumento file name de un fstream.

 58
Author: celtschk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-02 13:38:26

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

  1. Los constructores de la lista inicializadora tienen prioridad sobre otros constructores
  2. Todos los contenedores de biblioteca estándar y std::basic_string tienen constructores de lista inicializadora.
  3. La inicialización de llaves no permite reducir las conversiones.

Así que las llaves redondas y rizadas no son intercambiables. Pero saber dónde difieren me permite use la inicialización de curly sobre corchetes redondos en la mayoría de los casos (algunos de los casos en los que no puedo son errores del compilador).

 25
Author: juanchopanza,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-02 13:20:18

Fuera del código genérico (es decir, las plantillas), usted 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:

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 función:

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 los estilos T t = { init }; o T 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 un constructor explicit.

Para tipos que accept std::initializer_list aunque obviamente a veces se necesitan constructores no-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), ese último párrafo debería haber generado algunas advertencias. Considere 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)... } }; }

Entonces auto p = make_unique<std::vector<T>>(20, T {}); crea un vector de tamaño 2 si T es, por ejemplo, int, o un vector de tamaño 20 si T es std::string. Una señal muy reveladora de que algo está muy mal aquí es que no hay ningún rasgo que pueda salvarte 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 se remite a la inicialización directa si y solo si no hay ningún constructor tomando std::initializer_list interfiriendo. Del mismo modo std::is_convertible no sirve de nada.

He investigado si de hecho es posible hacer rodar a mano un rasgo que pueda arreglar eso, pero no soy demasiado optimista al respecto. En cualquier caso no creo que estaríamos faltando mucho, 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 uso llaves para la inicialización de valores (por ejemplo, T t {}; o T t = {};), lo cual es muy conveniente y creo que es superior a la forma de 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, la construcción predeterminada (T t; stream >> t; es el único caso en el que uso eso creo).

Eso no significa que todas las llaves son malas, sin embargo, 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 el std::unique_ptr<T>, a pesar de que el tipo real depende del parámetro de plantilla T.

 16
Author: Luc Danton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-02 15:30:35