c++ - reales - libro de android studio en español pdf
¿Por qué es mejor la inicialización de la lista(usar llaves) que las alternativas? (3)
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 en otro entero que no pueda mantener su valor. Por ejemplo, char to int está permitido, pero no int to char.
- Un valor de punto flotante no se puede convertir a otro tipo de punto flotante que no pueda mantener su valor. Por ejemplo, se permite flotar para duplicar, pero no para flotar.
- Un valor de punto flotante no se puede convertir en un tipo entero.
- Un valor entero no se puede convertir en 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)
}
La única situación en la que = se prefiere sobre {} es cuando se usa auto
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 la inicialización {} sobre las alternativas a menos que tenga una razón sólida para no hacerlo.
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 permítame responder mi propia pregunta.
Existen 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 , con la excepción del constructor predeterminado. Esto conduce a problemas con los constructores y las plantillas donde el constructor de tipo T
puede ser una lista de inicializadores 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 estas clases, hay pocas razones para no usar la lista de intializer.
Ya hay grandes respuestas sobre las ventajas de usar la inicialización de listas, sin embargo, mi regla de oro personal NO es usar llaves cuando sea posible, sino que depende de su 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 parece 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 por defecto 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 al 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 forma mucho más coherente que el uso de llaves por defecto, pero tener que recordar explícitamente todas las excepciones cuando no se pueden usar o tienen un significado diferente al de la sintaxis de llamada de función "normal" entre paréntesis (llama a una sobrecarga diferente).
Por ejemplo, se adapta muy bien a 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); //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