Considere o seguinte 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");
}
Não 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
Qual é a maneira correta de fazer isso e por quê?
(Também tentei chaves simples e duplas)
T t{42,3.14, "foo"}
?Respostas:
Para qualquer pessoa do futuro, esse comportamento será alterado no C ++ 20 .
Em outras palavras, embora a implementação internamente ainda chame
T(arg0, arg1, ...)
, será considerada regular comoT{arg0, arg1, ...}
você esperaria.fonte
Você precisa definir explicitamente um ctor para a classe:
#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"); }
O objetivo do uso
emplace_back
é evitar a criação de um objeto temporário, que é então copiado (ou movido) para o destino. Embora também seja possível criar um objeto temporário e, em seguida, passá-lo paraemplace_back
, ele anula (pelo menos a maior parte) o propósito. O que você quer fazer é passar argumentos individuais, então vamosemplace_back
chamar o ctor com esses argumentos para criar o objeto no lugar.fonte
T(int a, double b, string c) : a(a), b(b), c(std::move(c))
emplace_back
. Essa é a resposta correta. É assim queemplace*
funciona. Eles constroem o elemento no local usando os argumentos encaminhados. Portanto, um construtor é necessário para receber esses argumentos.c
por&&
se nada for feito com a sua possível rvalueness; no inicializador do membro, o argumento é tratado como um lvalue novamente, na ausência de um elenco, de modo que o membro apenas obtém uma cópia construída. Mesmo se o membro foi construído por movimento, não é idiomático exigir que os chamadores sempre passem um valor temporário oustd::move()
d (embora eu deva confessar que tenho alguns casos em meu código onde faço isso, mas apenas em detalhes de implementação) .Claro, isso não é uma resposta, mas mostra uma característica interessante das 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"); }
fonte
tuple
vez de definir uma estrutura POD, você obterá um construtor gratuitamente , o que significa que você obtém aemplace
sintaxe de graça (entre outras coisas - você também obtém ordenação lexicográfica). Você perde nomes de membros, mas às vezes é menos trabalhoso criar acessadores do que todo o resto do clichê que você precisaria de outra forma. Concordo que a resposta de Jerry Coffin é muito melhor do que a aceita. Eu também votei a favor anos atrás.pair
quase sempre com ... mas às vezes me pergunto se realmente ganho muito em termos líquidos, heh. Mas talveztuple
isso aconteça no futuro. Obrigado por expandir!Se você não deseja (ou não pode) adicionar um construtor, especialize o alocador para T (ou crie seu próprio alocador).
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)... }; } }; }
Observação: a construção da função de membro mostrada acima não pode ser compilada com o clang 3.1 (Desculpe, não sei por quê). Tente o próximo se for usar o clang 3.1 (ou outros motivos).
void construct(T* p, int a, double b, const string& c) { ::new(static_cast<void*>(p)) T{ a, b, c }; }
fonte
std::aligned_storage
Isso parece estar coberto em 23.2.1 / 13.
Primeiro, definições:
Agora, o que o torna construtível no local:
E, finalmente, uma observação sobre a implementação padrão da chamada de construção:
Isso nos diz que, para um esquema de alocador padrão (e potencialmente o único), você deve ter definido um construtor com o número adequado de argumentos para o que está tentando construir-empor em um contêiner.
fonte
você tem que definir um construtor para seu tipo
T
porque ele contém umstd::string
que não é trivial.além disso, seria melhor definir (possível inadimplente) mover ctor / atribuir (porque você tem um
std::string
membro móvel ) - isso ajudaria a mover seuT
...ou use apenas
T{...}
para chamar sobrecarregadoemplace_back()
conforme recomendado na resposta do neighboug ... tudo depende de seus casos de uso típicos ...fonte
emplace_back()
chamada :)Você pode criar a
struct T
instância e movê-la para o vetor:V.push_back(std::move(T {42, 3.14, "foo"}));
fonte
Você pode usar o
{}
sintaxe para inicializar o novo elemento:V.emplace_back(T{42, 3.14, "foo"});
Isso pode ou não ser otimizado, mas deveria ser.
Você tem que definir um construtor para que isso funcione, observe que com seu código você não pode nem fazer:
T a(42, 3.14, "foo");
Mas é disso que você precisa para que o emplace funcione.
então apenas:
struct T { ... T(int a_, double b_, string c_) a(a_), b(b_), c(c_) {} }
fará com que funcione da maneira desejada.
fonte
std::move
é necessário.T{42, 3.14, "foo"}
já será encaminhado por emplace_back e vinculado ao construtor struct move como um rvalue. No entanto, eu preferiria uma solução que o construísse no local.