¿Por qué la inicialización de la lista (usando llaves) es mejor que las alternativas?

6 minutos de lectura

¿Por que la inicializacion de la lista usando llaves es
Oleksiy

MyClass a1 {a};     // clearer and less error-prone than the other three
MyClass a2 = {a};
MyClass a3 = a;
MyClass a4(a);

¿Por qué?

  • No encajaría en el cuadro de comentarios;). De todos modos, para citar el artículo vinculado: “… las principales razones para declarar variables usando auto son la corrección, el rendimiento, la mantenibilidad y la robustez, y sí, la conveniencia…”.

    – Marcos García

    14 de agosto de 2013 a las 4:01

  • Eso es cierto, es conveniente, pero reduce la legibilidad en mi opinión – me gusta ver qué tipo es un objeto al leer código. Si está 100% seguro de qué tipo es el objeto, ¿por qué usar automático? Y si usa la inicialización de la lista (lea mi respuesta), puede estar seguro de que siempre es correcta.

    – Oleksiy

    14 de agosto de 2013 a las 4:04

  • @Oleksiy: std::map<std::string, std::vector<std::string>>::const_iterator quisiera hablar contigo.

    – Xeo

    14 de agosto de 2013 a las 6:03

  • @Oleksiy Recomiendo leer esto tiene.

    – Rapptz

    14 de agosto de 2013 a las 6:05

  • @doc yo diría using MyContainer = std::map<std::string, std::vector<std::string>>; es aún mejor (¡especialmente porque puedes crear una plantilla!)

    – JAB

    18 de febrero de 2016 a las 22:26


¿Por que la inicializacion de la lista usando llaves es
Oleksiy

Básicamente copiando y pegando de Bjarne Stroustrup “El lenguaje de programación C++ 4ª edición”:

Lista de inicialización no permite el estrechamiento (§iso.8.5.4). Es decir:

  • Un entero no se puede convertir en otro entero que no pueda contener su valor. Por ejemplo, se permite char a int, pero no int a char.
  • Un valor de punto flotante no se puede convertir a otro tipo de punto flotante que no pueda contener su valor. Por ejemplo, se permite float to double, pero no double to float.
  • Un valor de punto flotante no se puede convertir a un tipo entero.
  • Un valor entero no se puede convertir a un tipo de punto flotante.

Ejemplo:

void fun(double val, int val2) {

    int x2 = val;    // if val == 7.9, x2 becomes 7 (bad)

    char c2 = val2;  // if val2 == 1025, c2 becomes 1 (bad)

    int x3 {val};    // error: possible truncation (good)

    char c3 {val2};  // error: possible narrowing (good)

    char c4 {24};    // OK: 24 can be represented exactly as a char (good)

    char c5 {264};   // error (assuming 8-bit chars): 264 cannot be 
                     // represented as a char (good)

    int x4 {2.0};    // error: no double to int value conversion (good)

}

los solamente situación donde se prefiere = sobre {} es cuando se usa auto palabra clave para obtener el tipo determinado por el inicializador.

Ejemplo:

auto z1 {99};   // z1 is an int
auto z2 = {99}; // z2 is std::initializer_list<int>
auto z3 = 99;   // z3 is an int

Conclusión

Prefiere la inicialización de {} sobre las alternativas a menos que tenga una razón sólida para no hacerlo.

  • También está el hecho de que usar () se puede analizar como una declaración de función. Es confuso e inconsistente que puedas decir T t(x,y,z); pero no T t(). Y a veces, estás seguro xni siquiera puedes decir T t(x);.

    – juanchopanza

    14 de agosto de 2013 a las 5:23

  • Estoy totalmente en desacuerdo con esta respuesta; la inicialización con refuerzos se convierte en un completo desastre cuando tiene tipos con un ctor que acepta un std::initializer_list. RedXIII menciona este problema (y simplemente lo ignora), mientras que usted lo ignora por completo. A(5,4) y A{5,4} puede llamar a funciones completamente diferentes, y esto es algo importante que debe saber. Incluso puede resultar en llamadas que parecen poco intuitivas. Diciendo que deberías preferir {} por defecto llevará a que la gente malinterprete lo que está pasando. Sin embargo, esto no es culpa tuya. Personalmente, creo que es una característica extremadamente mal pensada.

    – usuario1520427

    2 de febrero de 2015 a las 1:40

  • @ user1520427 Es por eso que existe el “a menos que tengas una fuerte razón para no hacerlo” parte.

    – Oleksiy

    2 de febrero de 2015 a las 16:54

  • Aunque esta pregunta es antigua, tiene bastantes aciertos, por lo que estoy agregando esto aquí solo como referencia (no lo he visto en ningún otro lugar de la página). Desde C++14 con el nuevo Reglas para la deducción automática de braced-init-list ahora es posible escribir auto var{ 5 } y se deducirá como int no más como std::initializer_list<int>.

    –Edoardo Dominici

    31 de octubre de 2015 a las 12:26


  • Jaja, de todos los comentarios todavía no está claro qué hacer. Lo que está claro es que la especificación de C++ es un desastre.

    – DrumM

    12 de marzo de 2019 a las 13:19

1647558130 885 ¿Por que la inicializacion de la lista usando llaves es
mikemb

Ya hay excelentes respuestas sobre las ventajas de usar la inicialización de listas, sin embargo, mi regla general personal es NO usar llaves siempre que sea posible, sino hacer que dependa del significado conceptual:

  • Si el objeto que estoy creando contiene conceptualmente los valores que estoy pasando en el constructor (por ejemplo, contenedores, estructuras POD, atómicas, punteros inteligentes, etc.), entonces estoy usando las llaves.
  • Si el constructor se asemeja a una llamada de función normal (realiza algunas operaciones más o menos complejas que están parametrizadas por los argumentos), entonces estoy usando la sintaxis de llamada de función normal.
  • Para la inicialización predeterminada, siempre uso llaves.
    Por un lado, de esa manera siempre estoy seguro de que el objeto se inicializa independientemente de si, por ejemplo, es una clase “real” con un constructor predeterminado que se llamaría de todos modos o un tipo incorporado/POD. En segundo lugar, en la mayoría de los casos, es consistente con la primera regla, ya que un objeto inicializado predeterminado a menudo representa un objeto “vacío”.

En mi experiencia, este conjunto de reglas se puede aplicar de manera mucho más consistente que el uso predeterminado de llaves, pero tener que recordar explícitamente todas las excepciones cuando no se pueden usar o tienen un significado diferente que la sintaxis de llamada de función “normal” con paréntesis (llama a una sobrecarga diferente).

Por ejemplo, encaja muy bien con los tipos de biblioteca estándar como std::vector:

vector<int> a{10,20};   //Curly braces -> fills the vector with the arguments

vector<int> b(10,20);   //Parentheses -> uses arguments to parametrize some functionality,                          
vector<int> c(it1,it2); //like filling the vector with 10 integers or copying a range.

vector<int> d{};      //empty braces -> default constructs vector, which is equivalent
                      //to a vector that is filled with zero elements

  • Totalmente de acuerdo con la mayor parte de tu respuesta. Sin embargo, ¿no cree que poner llaves vacías para el vector es simplemente redundante? Quiero decir, está bien, cuando necesita inicializar un objeto de tipo T genérico, pero ¿cuál es el propósito de hacerlo para un código no genérico?

    – Mijaíl

    27 de noviembre de 2016 a las 11:15


  • @Mikhail: Ciertamente es redundante, pero tengo la costumbre de hacer siempre explícita la inicialización de variables locales. Como escribí, esto se trata principalmente de consistencia, así que no lo olvido, cuando realmente importa. Sin embargo, ciertamente no es nada que mencionaría en una revisión de código o incluiría en una guía de estilo.

    – Mike MB

    27 de noviembre de 2016 a las 16:10

  • conjunto de reglas bastante limpio.

    – laike9m

    20 de marzo de 2018 a las 5:57

  • Esta es, de lejos, la mejor respuesta. {} es como la herencia: fácil de abusar, lo que lleva a un código difícil de entender.

    – Mono del Reino Unido

    9 de mayo de 2018 a las 13:36


  • Ejemplo de @MikeMB: const int &b{} <- no intenta crear una referencia no inicializada, sino que la vincula a un objeto entero temporal. Segundo ejemplo: struct A { const int &b; A():b{} {} }; <- no intenta crear una referencia no inicializada (como () lo haría), pero vincúlelo a un objeto entero temporal y luego déjelo colgando. CCG incluso con -Wall no advierte para el segundo ejemplo.

    – Johannes Schaub – litb

    18 de noviembre de 2018 a las 18:05


1647558131 348 ¿Por que la inicializacion de la lista usando llaves es
Rojo XIII

Hay MUCHAS razones para usar la inicialización de llaves, pero debe tener en cuenta que los initializer_list<> se prefiere el constructor a los otros constructores, siendo la excepción el constructor predeterminado. Esto genera problemas con los constructores y las plantillas donde el tipo T constructor puede ser una lista de inicializadores o un simple ctor antiguo.

struct Foo {
    Foo() {}

    Foo(std::initializer_list<Foo>) {
        std::cout << "initializer list" << std::endl;
    }

    Foo(const Foo&) {
        std::cout << "copy ctor" << std::endl;
    }
};

int main() {
    Foo a;
    Foo b(a); // copy ctor
    Foo c{a}; // copy ctor (init. list element) + initializer list!!!
}

Suponiendo que no encuentre tales clases, hay pocas razones para no usar la lista de inicializadores.

  • Esto es un muy punto importante en la programación genérica. Cuando escribes plantillas, no use listas de inicio entre llaves (el nombre estándar para { ... }) a menos que desee la initializer_list semántica (bueno, y tal vez para la construcción predeterminada de un objeto).

    – Xeo

    14 de agosto de 2013 a las 7:11


  • Sinceramente, no entiendo por qué el std::initializer_list la regla incluso existe, solo agrega confusión y desorden al lenguaje. que tiene de malo hacer Foo{{a}} si quieres el std::initializer_list ¿constructor? Eso parece mucho más fácil de entender que tener std::initializer_list tienen prioridad sobre todas las demás sobrecargas.

    – usuario1520427

    2 de febrero de 2015 a las 1:48

  • +1 por el comentario anterior, ¡porque creo que es realmente un desastre! no es lógica; Foo{{a}} sigue algo de lógica para mí mucho más que Foo{a}que se convierte en precedencia de la lista de inicializadores (ups podría pensar el usuario hm…)

    – gabriel

    19/04/2015 a las 19:21

  • Básicamente, C++ 11 reemplaza un lío con otro lío. Oh, lo siento, no lo reemplaza, se suma a él. ¿Cómo puede saber si no encuentra tales clases? ¿Qué pasa si empiezas? sin std::initializer_list<Foo> constructor, pero va a ser agregado al Foo clase en algún momento para ampliar su interfaz? Entonces los usuarios de Foo la clase está jodida.

    -mip

    29 de mayo de 2015 a las 9:02


  • .. ¿Cuáles son las “MUCHAS razones para usar la inicialización de llaves”? Esta respuesta señala una razón (initializer_list<>), que en realidad no califica quién dice que es preferible, y luego procede a mencionar un buen caso donde es NO privilegiado. ¿Qué me estoy perdiendo que otras 30 personas (a partir del 21 de abril de 2016) encontraron útil?

    – dwanderson

    21 de abril de 2016 a las 14:19

1647558131 679 ¿Por que la inicializacion de la lista usando llaves es
allan jensen

Solo es más seguro siempre y cuando no construyas con -Wno-narrowing como dice Google en Chromium. Si lo hace, entonces es MENOS seguro. Sin esa bandera, los únicos casos inseguros serán corregidos por C++20.

Nota: A) Los corchetes son más seguros porque no permiten el estrechamiento. B) Los corchetes rizados son menos seguros porque pueden pasar por alto los constructores privados o eliminados, y llamar implícitamente a los constructores marcados explícitos.

Esos dos combinados significan que son más seguros si lo que hay dentro son constantes primitivas, pero menos seguros si son objetos (aunque arreglados en C++ 20)

¿Por que la inicializacion de la lista usando llaves es
codificación

Actualización (2022-02-11): tenga en cuenta que hay opiniones más recientes sobre ese tema además de la publicada originalmente (a continuación), que argumentan en contra de la preferencia del inicializador {}, como Arthur Dwyer en su publicación de blog sobre La pesadilla de la inicialización en C++.

Respuesta original:

Leer GotW #1 de Herb Sutter (actualizado). Esto explica en detalle la diferencia entre estas y algunas opciones más, junto con varios errores que son relevantes para distinguir el comportamiento de las diferentes opciones.

Lo esencial/copiado de la sección 4:

¿Cuándo debe usar la sintaxis ( ) frente a { } para inicializar objetos? ¿Por qué? Aquí está la guía simple:

Pauta: Prefiera usar la inicialización con { }, como el vector v = { 1, 2, 3, 4 }; o auto v = vector{ 1, 2, 3, 4 };, porque es más consistente, más correcto y evita tener que conocer las trampas del viejo estilo. En los casos de un solo argumento en los que prefiere ver solo el signo =, como int i = 42; y auto x = cualquier cosa; omitir las llaves está bien. …

Eso cubre la gran mayoría de los casos. Solo hay una excepción principal:

… En casos raros, como el vector v(10,20); o auto v = vector(10,20);, use la inicialización con ( ) para llamar explícitamente a un constructor que de otro modo estaría oculto por un constructor initializer_list.

Sin embargo, la razón por la que esto debería ser generalmente “raro” es porque la construcción por defecto y la copia ya son especiales y funcionan bien con { }, y un buen diseño de clase ahora evita principalmente el caso de recurrir a () para los constructores definidos por el usuario debido a esto guía de diseño final:

Pauta: cuando diseñe una clase, evite proporcionar un constructor que se sobrecargue de forma ambigua con un constructor initializer_list, de modo que los usuarios no necesiten usar ( ) para llegar a dicho constructor oculto.

Consulte también las Directrices básicas sobre ese tema: ES.23: Preferir la sintaxis del inicializador {}.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad