push_back - C++ 11 emplace_back en el vector<struct>?
c++ emplace back (6)
Considere el siguiente programa:
#include <string>
#include <vector>
using namespace std;
struct T
{
int a;
double b;
string c;
};
vector<T> V;
int main()
{
V.emplace_back(42, 3.14, "foo");
}
No funciona:
$ g++ -std=gnu++11 ./test.cpp
In file included from /usr/include/c++/4.7/x86_64-linux-gnu/bits/c++allocator.h:34:0,
from /usr/include/c++/4.7/bits/allocator.h:48,
from /usr/include/c++/4.7/string:43,
from ./test.cpp:1:
/usr/include/c++/4.7/ext/new_allocator.h: In instantiation of ‘void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = T; _Args = {int, double, const char (&)[4]}; _Tp = T]’:
/usr/include/c++/4.7/bits/alloc_traits.h:253:4: required from ‘static typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type std::allocator_traits<_Alloc>::_S_construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = T; _Args = {int, double, const char (&)[4]}; _Alloc = std::allocator<T>; typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type = void]’
/usr/include/c++/4.7/bits/alloc_traits.h:390:4: required from ‘static void std::allocator_traits<_Alloc>::construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = T; _Args = {int, double, const char (&)[4]}; _Alloc = std::allocator<T>]’
/usr/include/c++/4.7/bits/vector.tcc:97:6: required from ‘void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {int, double, const char (&)[4]}; _Tp = T; _Alloc = std::allocator<T>]’
./test.cpp:17:32: required from here
/usr/include/c++/4.7/ext/new_allocator.h:110:4: error: no matching function for call to ‘T::T(int, double, const char [4])’
/usr/include/c++/4.7/ext/new_allocator.h:110:4: note: candidates are:
./test.cpp:6:8: note: T::T()
./test.cpp:6:8: note: candidate expects 0 arguments, 3 provided
./test.cpp:6:8: note: T::T(const T&)
./test.cpp:6:8: note: candidate expects 1 argument, 3 provided
./test.cpp:6:8: note: T::T(T&&)
./test.cpp:6:8: note: candidate expects 1 argument, 3 provided
¿Cuál es la forma correcta de hacer esto y por qué?
(También se probaron brackets simples y dobles)
Esto parece estar cubierto en 23.2.1 / 13.
Primero, definiciones:
Dado un contenedor tipo X que tiene un allocator_type idéntico a A y un value_type idéntico a T y dado un lvalue m de tipo A, un puntero p de tipo T *, una expresión v de tipo T, y un rvalue rv de tipo T, el los siguientes términos están definidos.
Ahora, lo que lo hace emplace-constructible:
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 :: constructo (m, p, args);
Y finalmente, una nota sobre la implementación predeterminada de la llamada a constructo:
Nota: Un contenedor llama a allocator_traits :: constructo (m, p, args) para construir un elemento en p usando args. La construcción predeterminada en std :: allocator llamará a :: new ((void *) p) T (args), pero los asignadores especializados pueden elegir una definición diferente.
Esto nos dice que, para un esquema de asignación predeterminada (y potencialmente el único), debe haber definido un constructor con la cantidad adecuada de argumentos para lo que está intentando emplace-construir en un contenedor.
Necesitas definir explícitamente un ctor para la clase:
#include <string>
#include <vector>
using namespace std;
struct T
{
int a;
double b;
string c;
T(int a, double b, string &&c)
: a(a)
, b(b)
, c(std::move(c))
{}
};
vector<T> V;
int main()
{
V.emplace_back(42, 3.14, "foo");
}
El punto de usar emplace_back
es evitar la creación de un objeto temporal, que luego se copia (o mueve) al destino. Si bien también es posible crear un objeto temporal, luego pasarlo a emplace_back
, se derrota (al menos la mayoría de) el propósito. Lo que quiere hacer es pasar argumentos individuales, luego dejar que emplace_back
invoque el ctor con esos argumentos para crear el objeto en su lugar.
Por supuesto, esta no es una respuesta, pero muestra una característica interesante de las tuplas:
#include <string>
#include <tuple>
#include <vector>
using namespace std;
using T = tuple <
int,
double,
string
>;
vector<T> V;
int main()
{
V.emplace_back(42, 3.14, "foo");
}
Puede usar la sintaxis {}
para inicializar el nuevo elemento:
V.emplace_back(T{42, 3.14, "foo"});
Esto puede o no estar optimizado, pero debería serlo.
Tienes que definir un constructor para que esto funcione, ten en cuenta que con tu código ni siquiera puedes hacer:
T a(42, 3.14, "foo");
Pero esto es lo que necesita para tener un trabajo emplace.
por lo que sólo:
struct T {
...
T(int a_, double b_, string c_) a(a_), b(b_), c(c_) {}
}
lo hará funcionar de la manera deseada.
Si no desea (o no puede) agregar un constructor, especialice el asignador para T (o cree su propio asignador).
namespace std {
template<>
struct allocator<T> {
typedef T value_type;
value_type* allocate(size_t n) { return static_cast<value_type*>(::operator new(sizeof(value_type) * n)); }
void deallocate(value_type* p, size_t n) { return ::operator delete(static_cast<void*>(p)); }
template<class U, class... Args>
void construct(U* p, Args&&... args) { ::new(static_cast<void*>(p)) U{ std::forward<Args>(args)... }; }
};
}
Nota: La construcción de función miembro que se muestra arriba no puede compilarse con clang 3.1 (Lo siento, no sé por qué). Pruebe el siguiente si usará clang 3.1 (u otras razones).
void construct(T* p, int a, double b, const string& c) { ::new(static_cast<void*>(p)) T{ a, b, c }; }
tienes que definir un constructor para tu tipo T
porque contiene un std::string
que no es trivial.
además, sería mejor definir (posible incumplimiento) move ctor / assign (porque tienes una std::string
móvil como miembro) - esto ayudaría a mover tu T
mucho más eficiente ...
o, simplemente use T{...}
para llamar a emplace_back()
sobrecargado como se recomienda en la respuesta de vecinos ... todo depende de los casos de uso típicos ...