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


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

Por Qué?

No pude encontrar una respuesta en SO, así que déjame responder a mi propia pregunta.

Author: moooeeeep, 2013-08-14

3 answers

Básicamente copiando y pegando desde "The C++ Programming Language 4th Edition "de Bjarne Stroustrup":

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

  • Un entero no se puede convertir a otro entero que no pueda mantener su valor. Por ejemplo, char to int está permitido, pero no int to char.
  • Un valor de coma flotante no se puede convertir a otro tipo de coma flotante que no pueda contener su valor. Por ejemplo, flotar a se permite el doble, pero no el doble para flotar.
  • Un valor de coma flotante no se puede convertir a un tipo entero.
  • Un valor entero no se puede convertir a un tipo de coma 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)

}

La única situación en la que se prefiere = a {} es cuando se usa la palabra clave auto para obtener el tipo determinado por el inicializador.

Ejemplo:

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

Conclusión

Prefiera {} inicialización sobre alternativas a menos que tengas una fuerte razón para no hacerlo.

 233
Author: Oleksiy,
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
2013-08-14 03:56:12

Hay MUCHAS razones para usar la inicialización de llaves, pero debe tener en cuenta que el constructor initializer_list<> es preferido a los otros constructores, la excepción es el constructor por defecto. Esto conduce a problemas con los constructores y plantillas donde el constructor type T puede ser una lista inicializadora o un 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 intializer.

 74
Author: Red XIII,
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
2013-08-14 07:24:46

Ya hay grandes 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 hacerlo dependiente del significado conceptual:

  • Si el objeto que estoy creando contiene conceptualmente los valores que estoy pasando en el constructor (por ejemplo, contenedores, estructuras POD, átomos, punteros inteligentes, etc.), entonces estoy usando los frenos.
  • Si el constructor se asemeja a una llamada a función realiza algunas operaciones más o menos complejas que son parametrizadas por los argumentos) entonces estoy usando la sintaxis normal de la llamada a la función.
  • 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 builtin / POD. En segundo lugar, es - en la mayoría de los casos-consistente con la primera regla, como un objeto inicializado predeterminado a menudo representa un objeto "vacío".

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

Por ejemplo, encaja muy bien con 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);   //Parentesis -> uses arguments to parameterize 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
 56
Author: MikeMB,
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
2016-09-10 10:25:04