push_back vs emplace_back

762

Estou um pouco confuso sobre a diferença entre push_backe emplace_back.

void emplace_back(Type&& _Val);
void push_back(const Type& _Val);
void push_back(Type&& _Val);

Como há uma push_backsobrecarga tomando uma referência rvalue, não vejo bem qual é o objetivo de emplace_backse tornar?

ronag
fonte
11
Algumas boas leituras aqui: open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2642.pdf
Johan Kotlinski
16
Observe que (como Thomas diz abaixo), o código na pergunta é da emulação de C ++ 0x do MSVS , e não o que realmente é o C ++ 0x.
ME22
5
Um artigo melhor para ler seria: open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2345.pdf . N2642 é principalmente redação para o Padrão; N2345 é o artigo que explica e motiva a ideia.
Alan
Observe que, mesmo no MSVC10, existe uma template <class _Valty> void emplace_back(_Valty&& _Val)versão que usa uma referência universal que fornece encaminhamento perfeito para explicitconstrutores de argumento único.
Joki 11/07
Relacionado: Existe algum caso em que push_backé preferível emplace_back? O único caso em que consigo pensar é se uma classe era de alguma forma copiável ( T&operator=(constT&)), mas não construtiva ( T(constT&)), mas não consigo pensar por que alguém iria querer isso.
Ben

Respostas:

569

Além do que o visitante disse:

A função void emplace_back(Type&& _Val)fornecida pelo MSCV10 não é conforme e é redundante, porque, como você observou, é estritamente equivalente a push_back(Type&& _Val).

Mas a verdadeira forma de C ++ 0x de emplace_backé realmente útil void emplace_back(Args&&...):;

Em vez de pegar um, value_typeé preciso uma lista variada de argumentos, o que significa que agora você pode encaminhar perfeitamente os argumentos e construir diretamente um objeto em um contêiner sem um temporário.

Isso é útil porque, não importa quanta inteligência o RVO e a movimentação semântica tragam para a tabela, ainda existem casos complicados em que um push_back provavelmente fará cópias desnecessárias (ou mover). Por exemplo, com a insert()função tradicional de a std::map, você deve criar um temporário, que será copiado para a std::pair<Key, Value>, que será copiado para o mapa:

std::map<int, Complicated> m;
int anInt = 4;
double aDouble = 5.0;
std::string aString = "C++";

// cross your finger so that the optimizer is really good
m.insert(std::make_pair(4, Complicated(anInt, aDouble, aString))); 

// should be easier for the optimizer
m.emplace(4, anInt, aDouble, aString);

Então, por que eles não implementaram a versão correta do emplace_back no MSVC? Na verdade, isso me incomodou há um tempo atrás, então fiz a mesma pergunta no blog do Visual C ++ . Aqui está a resposta de Stephan T Lavavej, mantenedor oficial da implementação da biblioteca padrão do Visual C ++ na Microsoft.

P: As funções beta 2 emplace são apenas algum tipo de espaço reservado no momento?

R: Como você deve saber, modelos variados não são implementados no VC10. Nós os simulamos com máquinas de pré-processador para coisas como make_shared<T>()tupla e coisas novas <functional>. Esse maquinário de pré-processador é relativamente difícil de usar e manter. Além disso, afeta significativamente a velocidade de compilação, pois precisamos incluir repetidamente sub-cabeçalhos. Devido a uma combinação de restrições de tempo e preocupações com a velocidade de compilação, não simulamos modelos variados em nossas funções de substituição.

Quando modelos variados são implementados no compilador, você pode esperar que possamos tirar proveito deles nas bibliotecas, inclusive em nossas funções emplace. Levamos a conformidade muito a sério, mas, infelizmente, não podemos fazer tudo de uma vez.

É uma decisão compreensível. Todo mundo que tentou apenas emular uma vez o modelo variado com truques horríveis do pré-processador sabe como essas coisas ficam nojentas.

Thomas Petit
fonte
101
Esse esclarecimento de que é um problema do MSVS10, não de C ++ é a parte mais importante aqui. Obrigado.
precisa saber é
11
Acredito que sua última linha de código C ++ não funcione. pair<const int,Complicated>não possui um construtor que usa int, outro int, um double e, como quarto parâmetro, uma string. No entanto, você pode construir diretamente esse objeto de par usando seu construtor por partes. A sintaxe será diferente, é claro:m.emplace(std::piecewise,std::forward_as_tuple(4),std::forward_as_tuple(anInt,aDouble,aString));
sellibitze 14/03
3
Felizmente, os modelos variados estarão no VS2013, agora em pré-visualização.
Daniel Earwicker
11
essa resposta deve ser atualizada para refletir os novos desenvolvimentos no vs2013?
Becko
6
Se você estiver usando o Visual Studio 2013 ou posterior agora , deverá ter suporte para o "real" emplace_back, desde que tenha sido implementado no Visual C ++ quando foram adicionados modelos variados
kayleeFrye_onDeck
200

emplace_backnão deve aceitar um argumento do tipo vector::value_type, mas argumentos variados que são encaminhados ao construtor do item anexado.

template <class... Args> void emplace_back(Args&&... args); 

É possível passar um value_typeque será encaminhado ao construtor de cópias.

Como encaminha os argumentos, isso significa que, se você não tiver rvalue, isso ainda significa que o contêiner armazenará uma cópia "copiada", não uma cópia movida.

 std::vector<std::string> vec;
 vec.emplace_back(std::string("Hello")); // moves
 std::string s;
 vec.emplace_back(s); //copies

Mas o acima deve ser idêntico ao que push_backfaz. Provavelmente é destinado a casos de uso como:

 std::vector<std::pair<std::string, std::string> > vec;
 vec.emplace_back(std::string("Hello"), std::string("world")); 
 // should end up invoking this constructor:
 //template<class U, class V> pair(U&& x, V&& y);
 //without making any copies of the strings
Visitante
fonte
2
@ David: mas então você mudou sde escopo, não é perigoso?
Matthieu M.
2
Não é perigoso se você não planeja usar s por mais tempo. Mover não invalida s, o movimento só roubará a alocação de memória interna já realizada em s e o deixará em um estado padrão (sem sting alocado) que, quando destruído, será bom como se você tivesse digitado std :: string str;
David
4
@ David: Não tenho certeza de que um objeto movido de seja necessário para ser válido para qualquer uso, exceto destruição subseqüente.
Ben Voigt
46
vec.emplace_back("Hello")funcionará, pois o const char*argumento será encaminhado ao stringconstrutor. Este é o ponto principal de emplace_back.
Alexandre C.
8
@BenVoigt: é necessário que um objeto movido esteja em um estado válido (mas não especificado). Isso não significa necessariamente que você pode executar qualquer operação nele. Considere std::vector. Um vazio std::vectoré um estado válido, mas você não pode chamá front()-lo. Isso significa que qualquer função que não tem pré-condições ainda pode ser chamada (e os destruidores nunca podem ter pré-condições).
David Stone
96

A otimização para emplace_backpode ser demonstrada no próximo exemplo.

Para emplace_backconstrutor A (int x_arg)será chamado. E para push_back A (int x_arg)é chamado primeiro e move A (A &&rhs)é chamado depois.

Obviamente, o construtor deve ser marcado como explicit, mas no exemplo atual é bom remover a explicitação.

#include <iostream>
#include <vector>
class A
{
public:
  A (int x_arg) : x (x_arg) { std::cout << "A (x_arg)\n"; }
  A () { x = 0; std::cout << "A ()\n"; }
  A (const A &rhs) noexcept { x = rhs.x; std::cout << "A (A &)\n"; }
  A (A &&rhs) noexcept { x = rhs.x; std::cout << "A (A &&)\n"; }

private:
  int x;
};

int main ()
{
  {
    std::vector<A> a;
    std::cout << "call emplace_back:\n";
    a.emplace_back (0);
  }
  {
    std::vector<A> a;
    std::cout << "call push_back:\n";
    a.push_back (1);
  }
  return 0;
}

resultado:

call emplace_back:
A (x_arg)

call push_back:
A (x_arg)
A (A &&)
vadikrobot
fonte
21
+1 para o exemplo de código que demonstra o que realmente acontece ao chamar emplace_backvs push_back.
Shawn
Eu vim aqui depois de perceber que eu tinha um código que chamava v.emplace_back(x);onde x é explicitamente movível-construtível, mas apenas explicitamente copiável-construtível. O fato de emplace_backser "implicitamente" explícito me faz pensar que minha função principal de anexar provavelmente deveria ser push_back. Pensamentos?
Ben
Se você ligar pela a.emplace_backsegunda vez, o construtor de movimentação será chamado!
X Æ A-12
8

emplace_backa implementação em conformidade encaminhará argumentos para o vector<Object>::value_typeconstrutor quando adicionado ao vetor. Lembro-me de que o Visual Studio não suportava modelos variados, mas com modelos variados será suportado no Visual Studio 2013 RC, então acho que uma assinatura conforme será adicionada.

Com emplace_back, se você encaminhar os argumentos diretamente para o vector<Object>::value_typeconstrutor, não precisa de um tipo para ser móvel ou copiável para a emplace_backfunção, estritamente falando. No vector<NonCopyableNonMovableObject>caso, isso não é útil, pois vector<Object>::value_type precisa de um tipo copiável ou móvel para crescer.

Mas observe que isso pode ser útil std::map<Key, NonCopyableNonMovableObject>, pois, uma vez que você aloca uma entrada no mapa, ela não precisa mais ser movida ou copiada, ao contrário de vector, o que significa que você pode usar std::mapefetivamente com um tipo mapeado que não é copiável nem móvel.

Germán Diago
fonte
8

Mais um no caso de listas:

// constructs the elements in place.                                                
emplace_back("element");


//It will create new object and then copy(or move) its value of arguments.
push_back(explicitDataType{"element"});
HackSlash
fonte
1

Caso de uso específico para emplace_back: Se você precisar criar um objeto temporário que será empurrado para um contêiner, use em emplace_backvez de push_back. Ele criará o objeto no local dentro do contêiner.

Notas:

  1. push_backno caso acima, criará um objeto temporário e o moverá para o contêiner. No entanto, a construção no local usada emplace_backseria mais eficiente do que construir e depois mover o objeto (o que geralmente envolve algumas cópias).
  2. Em geral, você pode usar em emplace_backvez de push_backem todos os casos sem muito problema. (Veja exceções )
vaibhav kumar
fonte