Sintaxis de emplace_back y push_back c++ 11 con struct
c++ emplace back (1)
Estoy usando MSVC, Visual Studio 2013.
Supongamos que tengo una estructura:
struct my_pair {
int foo, bar;
};
Y quiero agregar un montón de estos de manera eficiente, sin crear demasiado un temporal y luego descartarlo:
vector<my_pair> v;
v.push_back(41, 42); // does not work [a]
v.push_back({41,42}); // works [b]
v.emplace_back(41,42); // does not work [c]
v.emplace_back({41,42}); // does not work [d]
v.emplace_back(my_pair{41,42}); //works [e]
Ahora si agrego un constructor y copio el constructor a mi código:
my_pair(int foo_, int bar_) : foo(foo_), bar(bar_)
{
cout << "in cstor" << endl;
}
my_pair(const my_pair& copy) : foo(copy.foo), bar(copy.bar)
{
cout << "in copy cstor" << endl;
}
Entonces el comportamiento cambia:
v.push_back(41, 42); // does not work [f]
v.push_back({41,42}); // displays "in cstor" and "in copy cstor" [g]
v.emplace_back(41,42); // displays "in cstor" [h]
v.emplace_back({41,42}); // does not work [i]
v.emplace_back(my_pair{41,42}); // "in cstor" and "in copy cstor" [j]
Si agrego un constructor de movimiento:
my_pair(my_pair&& move_) : foo(move_.foo), bar(move_.bar)
{
cout << "in move cstor" << endl;
}
Entonces:
v.emplace_back(my_pair{41,42}); //displays "in cstor", "in move cstor" [k]
v.emplace_back({41,42}); // still does not work [l]
v.push_back({41,42}); // displays "in cstor", "in move cstor" [m]
Preguntas:
para [a, b] entiendo la razón para trabajar y no trabajar.
para [c] , no funciona porque no hay un constructor para enviar los argumentos a.
para [d] , ¿por qué no funciona esto como en el caso de push?
para [e] , ¿por qué funciona cuando se agrega el nombre de la clase?
para [h] , parece que este es el código más eficiente si hay un constructor que asigna los argumentos a los miembros
para [j] , parece que esto es tan malo como un push_back y con tecleado adicional no estoy seguro de por qué alguien debería hacer esto sobre push_back
para [k, m] , con la adición de un constructor de movimientos parece que se está push_back(T&&)
lo que da como resultado el mismo rendimiento que emplace. Pero una vez más, con la escritura adicional no estoy seguro de por qué alguien haría esto.
Leí que MSVC no agrega un constructor de movimiento para usted: ¿Por qué se llama al constructor de copia en call a std :: vector :: emplace_back ()?
¿Cuál es la diferencia entre [d, e] y por qué es muy delicado al respecto? ¿Y por qué funciona push_back(T&&)
sin agregar el nombre de la estructura?
Solo puedo obtener todos los beneficios de emplace si sé que hay un constructor que toma a cada miembro como argumento.
¿Debo seguir con push_back
? ¿Hay alguna razón para usar emplace_back(structname{1,2,3})
lugar de push_back({1,2,3})
porque de todos modos terminará llamando push_back(T&&)
y es más fácil de escribir?
Tercero, ¿cómo hace emplace_back(arg1,arg2,etc)
para evitar la copia o mover el constructor por completo?
Para v.emplace_back({41,42});
, vea cómo usar std :: vector :: emplace_back para vector <vector <int>>?
v.emplace_back(41,42);
no funciona debido a algunas reglas en el estándar (un poco de énfasis mío):
Tabla 101 - Operaciones de contenedor de secuencia opcional
Expresión:
a.emplace_back(args)
Tipo de devolución:
void
Semántica operacional:
Añade un objeto de tipoT
construido constd::forward<Args>(args)...
Requiere: T será EmplaceConstructible enX
desdeargs
. Paravector
, T también será MoveInsertable en X.
Para que un tipo sea EmplaceConstructible
,
§ 23.2.1.13
- T es EmplaceConstructible en X desde args, para cero o más argumentos args, significa que la siguiente expresión está bien formada:
allocator_traits<A>::construct(m, p, args);
std::allocator_traits::construct()
a su vez hace (si es posible) a.construct(p, std::forward<Args>(args)...)
(donde a
es m
en la expresión EmplaceConstructible ).
a.construct()
aquí está std::allocator::construct()
, que llama a ::new((void *)p) U(std::forward<Args>(args)...)
. Esto es lo que causa el error de compilación.
U(std::forward<Args>(args)...)
(tome nota del uso de la inicialización directa ) encontrará un constructor de U
que acepta los argumentos reenviados. Sin embargo, en su caso, my_pair
es un tipo agregado, que solo se puede inicializar con la sintaxis de inicialización reforzada (inicialización agregada).
v.emplace_back(my_pair{41,42});
funciona porque llama al constructor de copia predeterminado implícitamente generado o al constructor de movimiento (tenga en cuenta que estos dos no siempre se pueden generar). my_pair
se construye un my_pair
temporal que pasa por el mismo proceso que el de v.emplace_back(41,42);
, solo que el argumento es un r-value my_pair
.
ADICIONAL 1:
¿Y por qué funciona push_back (T&&) sin agregar el nombre de la estructura?
Es debido a las push_back
de push_back
. El argumento de push_back()
no se deduce, lo que significa que al hacer push_back({1, 2})
, se crea un objeto temporal con el tipo de elemento del vector y se inicializa con {1, 2}
. Ese objeto temporal será entonces el que se pasa a push_back(T&&)
.
¿Debo seguir con push_back? ¿Hay alguna razón para usar emplace_back (structname {1,2,3}) en lugar de push_back ({1,2,3}) porque de todos modos terminará llamando push_back (T&&) y es más fácil de escribir?
Básicamente, las funciones emplace*
están diseñadas para optimizar y eliminar el costo de crear temporarios y copiar o mover objetos de construcción al insertarlos. Sin embargo, para el caso de los tipos de datos agregados donde hacer algo como emplace_back(1, 2, 3)
no es posible, y la única forma en que podría insertarlos es a través de la creación de un emplace_back(1, 2, 3)
temporal que luego se copia o se mueve, entonces, por supuesto, prefiera Sintaxis más ágil, y vaya a push_back({1,2,3})
, donde básicamente tendría el mismo rendimiento que emplace_back(structname{1,2,3})
.