C++ 0x inicialización uniforme "rareza"
c++11 uniform-initialization (4)
Como muchos, estoy muy entusiasmado con C++0x . Intento aprender y usar las nuevas características en nuevos proyectos para poder escribir el mejor código, el más fácil de mantener.
No hace falta decir que me encanta la idea detrás de los nuevos inicializadores. Así que los estoy mirando, y estos tienen sentido para mí:
T x = { 1, 2, 3 }; // like a struct or native array
T x({1, 2, 3}); // copy construct something like an "object literal" in other languages... cool!
return {1, 2, 3}; // similar to above, but returning it, even cooler!
Lo que no tiene sentido para mí es esto:
T x{1, 2, 3};
Simplemente se siente ... raro. No estoy seguro de qué sintaxis que la gente quiere usar, esto es mímico, simplemente no parece "correcto".
¿Cuál es el diseño / pensamiento detrás de esta sintaxis?
El único ejemplo donde parece que hace una diferencia es algo como esto:
std::vector<int> the_vec{4};
que llamaría al constructor de la lista de inicializadores, pero ¿por qué no escribir esto entonces?
std::vector<int> the_vec = {4};
¿Y con lo que todos ya están cómodos?
¿Cuál es el diseño / pensamiento detrás de esta sintaxis?
Por un lado, la sintaxis del corsé permite evitar los análisis molestos:
T x(); // function declaration
T x{}; // value-initialized object of type ''T'' named ''x''
En C ++ 03, lo más cerca que puedes llegar a esto es T x((T()));
o T x = T();
, ambos requieren que T
tenga un constructor de copia accesible.
Creo que la uniformidad sintáctica es bastante importante en la programación genérica. Por ejemplo, considere,
#include <utility>
#include <tuple>
#include <vector>
template <class T>
struct Uniform {
T t;
Uniform() : t{10, 12} {}
};
int main(void)
{
Uniform<std::pair<int, int>> p;
Uniform<std::tuple<int, int>> t;
Uniform<int [2]> a;
Uniform<std::vector<int>> v; // Uses initializer_list
return 0;
}
Las respuestas dadas son excelentes desde un punto de vista teórico, pero quizás algunos ejemplos prácticos también serían útiles. Con una inicialización uniforme ahora es posible escribir construcciones que antes eran simplemente imposibles. Por ejemplo:
Inicializar matrices de miembros.
Contenedores constantes globales (por ejemplo, mapas).
Esto es:
class Foo
{
int data[3];
public:
Foo() : data{1,2,3} { }
};
Aquí podemos inicializar la matriz de miembros directamente sin la necesidad de asignación (tenga en cuenta las situaciones en las que la construcción predeterminada no está disponible).
const std::map<int, std::string> labels {
{ 1 , "Open" },
{ 2 , "Close" },
{ 3 , "Reboot" } };
A veces, un objeto de búsqueda global de solo lectura es útil, pero no puede llenarlo con datos sin una inicialización uniforme.
Primero, realmente tienes dos variantes de una cosa:
T x = { 1, 2, 3 };
T x{1, 2, 3};
Estos dos realmente están haciendo la misma inicialización con la excepción de que el primero no es válido si selecciona un constructor explicit
. De lo contrario son idénticos. El primero se llama "copia de inicialización de lista" y el segundo es "inicialización de lista directa".
El concepto es que la forma con el =
está asignando un "valor compuesto", un valor que consta de 3 pulgadas. Y se inicializa x
con ese valor. Para tal inicialización, solo deben permitirse constructores no explicit
. El concepto para x{1, 2, 3}
(sin un signo igual) es que inicializa la variable con 3 valores, conceptualmente no es un valor compuesto, sino 3 valores separados que puede dar todos a la vez. Se podría decir que es una "llamada del constructor" en el sentido más general de ese término.
La otra inicialización que mostró es realmente algo completamente diferente de los dos anteriores:
T x({1, 2, 3});
Solo llama a los constuctores de T
con {1, 2, 3}
como argumento. No hace ninguna de las cosas extravagantes, como inicializar una matriz si T
es una matriz o inicializar miembros de estructura si T
es una estructura / clase agregada. Si T
no es una clase, esa declaración no es válida. Pero si T
tiene un constructor de copia o movimiento, a su vez puede usar ese constructor para construir una T
temporal mediante la inicialización de la lista de copia y vincular el parámetro de referencia del constructor de copia / movimiento a ese temporal. Creo que no necesitarás esa forma a menudo en código real.
Todo esto se registra en los documentos de propuesta del comité para las listas de inicialización. En este caso, desea consultar las Listas de inicializadores - Mecanismo alternativo y justificación , en la sección "Vista de un programador de los tipos de inicialización":
Hemos observado que los programadores expertos que son conscientes de una diferencia entre la inicialización de la copia y la inicialización directa a menudo piensan erróneamente que la primera es menos eficiente que la segunda. (En la práctica, cuando ambas inicializaciones tienen sentido, son igualmente eficientes).
Encontramos, en contraste, que es más útil pensar en estas cosas en diferentes términos:
- construyendo llamando a un constructor (una "llamada ctor")
- construcción mediante la transferencia de un valor (una "conversión")
(Como sucede, el primero corresponde a la "inicialización directa", y el segundo a la "inicialización de la copia", pero los términos estándar no ayudan al programador).
Más tarde, encuentran
Tenga en cuenta que ya tratamos el
{ ... }
en
X x = { ... };
como un solo valor, no es equivalente a
X x{ ... };
donde el
{ ... }
es una lista de argumentos para la llamada del constructor (lo enfatizamos porque es diferente a N2531).
Las reglas establecidas en el FDIS de C ++ 0x son ligeramente diferentes a las presentadas en ese documento, pero la justificación presentada en ese documento se mantiene e implementa en el FDIS de C ++ 0x.