push_back vs emplace_back

763

Je suis un peu confus quant à la différence entre push_backet emplace_back.

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

Comme il y a une push_backsurcharge prenant une référence rvalue, je ne vois pas vraiment à quoi sert le but emplace_back?

ronag
la source
11
Bonne lecture ici: open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2642.pdf
Johan Kotlinski
16
Notez que (comme Thomas le dit ci-dessous), le code de la question provient de l' émulation MSVS de C ++ 0x, et non de ce qu'est réellement C ++ 0x.
me22
5
Un meilleur article à lire serait: open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2345.pdf . N2642 est principalement libellé pour la norme; N2345 est le papier qui explique et motive l'idée.
Alan
Notez que même dans MSVC10, il existe une template <class _Valty> void emplace_back(_Valty&& _Val)version qui prend une référence universelle qui fournit une transmission parfaite aux explicitconstructeurs à argument unique.
joki
Connexes: Y a-t-il des cas où cela push_backest préférable emplace_back? Le seul cas auquel je peux penser est si une classe était en quelque sorte copiable ( T&operator=(constT&)) mais pas constructible ( T(constT&)), mais je ne vois pas pourquoi on voudrait jamais ça.
Ben

Réponses:

570

En plus de ce que le visiteur a dit:

La fonction void emplace_back(Type&& _Val)fournie par MSCV10 est non conforme et redondante, car comme vous l'avez noté, elle est strictement équivalente à push_back(Type&& _Val).

Mais la vraie forme C ++ 0x de emplace_backest vraiment utile void emplace_back(Args&&...):;

Au lieu de prendre un, value_typeil prend une liste variadique d'arguments, ce qui signifie que vous pouvez maintenant parfaitement transmettre les arguments et construire directement un objet dans un conteneur sans aucun temporaire.

C'est utile parce que, quelle que soit l'intelligence RVO et le déplacement sémantique apportés à la table, il y a encore des cas compliqués où un push_back est susceptible de faire des copies inutiles (ou de se déplacer). Par exemple, avec la insert()fonction traditionnelle de a std::map, vous devez créer un temporaire, qui sera ensuite copié dans un std::pair<Key, Value>, qui sera ensuite copié dans la carte:

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);

Alors pourquoi n'ont-ils pas implémenté la bonne version de emplace_back dans MSVC? En fait, cela m'a trop dérangé il y a un certain temps, j'ai donc posé la même question sur le blog Visual C ++ . Voici la réponse de Stephan T Lavavej, le responsable officiel de l'implémentation de la bibliothèque standard Visual C ++ chez Microsoft.

Q: Les fonctions de mise en place de la version bêta 2 ne sont-elles qu'une sorte d'espace réservé actuellement?

R: Comme vous le savez peut-être, les modèles variadic ne sont pas implémentés dans VC10. Nous les simulons avec des machines de préprocesseur pour des choses comme le make_shared<T>()tuple et les nouveautés <functional>. Cette machinerie de préprocesseur est relativement difficile à utiliser et à entretenir. En outre, cela affecte considérablement la vitesse de compilation, car nous devons inclure à plusieurs reprises des sous-titres. En raison d'une combinaison de nos contraintes de temps et de nos problèmes de vitesse de compilation, nous n'avons pas simulé de modèles variadiques dans nos fonctions d'emplace.

Lorsque des modèles variadiques sont implémentés dans le compilateur, vous pouvez vous attendre à en profiter dans les bibliothèques, y compris dans nos fonctions d'emplace. Nous prenons la conformité très au sérieux, mais malheureusement, nous ne pouvons pas tout faire en même temps.

C'est une décision compréhensible. Tous ceux qui ont essayé une seule fois d'émuler un modèle variadic avec des trucs horribles de préprocesseur savent à quel point ce truc est dégoûtant.

Thomas Petit
la source
101
Cette clarification que c'est un problème MSVS10, pas un problème C ++ est la partie la plus importante ici. Merci.
me22
11
Je crois que votre dernière ligne de code C ++ ne fonctionnera pas. pair<const int,Complicated>n'a pas de constructeur qui prend un int, un autre int, un double et comme 4ème paramètre une chaîne. Cependant, vous pouvez directement construire cet objet paire à l'aide de son constructeur par morceaux. La syntaxe sera bien sûr différente:m.emplace(std::piecewise,std::forward_as_tuple(4),std::forward_as_tuple(anInt,aDouble,aString));
sellibitze
3
Heureusement, les modèles variadic seront dans VS2013, maintenant en aperçu.
Daniel Earwicker
11
cette réponse doit-elle être mise à jour pour refléter les nouveaux développements de vs2013?
Becko
6
Si vous utilisez Visual Studio 2013 ou version ultérieure maintenant , vous devez prendre en charge le «réel» emplace_backtant qu'il a été implémenté dans Visual C ++ lorsque des modèles variadic
kayleeFrye_onDeck
200

emplace_backne doit pas prendre un argument de type vector::value_type, mais plutôt des arguments variadiques qui sont transmis au constructeur de l'élément ajouté.

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

Il est possible de passer un value_typequi sera transmis au constructeur de copie.

Parce qu'il transmet les arguments, cela signifie que si vous n'avez pas rvalue, cela signifie toujours que le conteneur stockera une copie "copiée", pas une copie déplacée.

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

Mais ce qui précède devrait être identique à ce qui le push_backfait. Il est probablement plutôt destiné à des cas d'utilisation tels que:

 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
visiteur
la source
2
@David: mais alors vous avez une sportée déplacée , n'est-ce pas dangereux?
Matthieu M.
2
Ce n'est pas dangereux si vous ne prévoyez plus d'utiliser s pour sa valeur. Le déplacement ne rend pas s invalide, le déplacement ne volera que l'allocation de mémoire interne déjà effectuée dans s et la laissera dans un état par défaut (aucune piqûre allouée) qui, une fois détruit, ira bien comme si vous veniez de taper std :: string str;
David
4
@David: Je ne suis pas sûr qu'un objet déplacé doit être valide pour toute utilisation, sauf destruction ultérieure.
Ben Voigt
46
vec.emplace_back("Hello")fonctionnera, puisque l' const char*argument sera transmis au stringconstructeur. C'est tout l'intérêt de emplace_back.
Alexandre C.
8
@BenVoigt: Un objet déplacé doit être dans un état valide (mais non spécifié). Cependant, cela ne signifie pas nécessairement que vous pouvez effectuer n'importe quelle opération. Considérez std::vector. Un vide std::vectorest un état valide, mais vous ne pouvez pas l'invoquer front(). Cela signifie que toute fonction qui n'a pas de conditions préalables peut toujours être appelée (et les destructeurs ne peuvent jamais avoir de conditions préalables).
David Stone
97

L'optimisation pour emplace_backpeut être démontrée dans l'exemple suivant.

Pour emplace_backconstructeur A (int x_arg)sera appelé. Et pour push_back A (int x_arg)est appelé en premier et move A (A &&rhs)est appelé ensuite.

Bien sûr, le constructeur doit être marqué comme explicit, mais pour l'exemple actuel, il est bon de supprimer l'explicitness.

#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;
}

production:

call emplace_back:
A (x_arg)

call push_back:
A (x_arg)
A (A &&)
vadikrobot
la source
21
+1 pour l'exemple de code qui montre ce qui se passe réellement lors de l' appel emplace_backvs push_back.
Shawn
Je suis venu ici après avoir remarqué que j'avais du code qui appelait v.emplace_back(x);où x est explicitement constructible par déplacement mais uniquement explicitement constructible par copie. Le fait que ce emplace_backsoit "implicitement" explicite me fait penser que ma fonction go-to pour l'ajout devrait probablement l'être push_back. Pensées?
Ben
Si vous appelez une a.emplace_backdeuxième fois, le constructeur de déplacement sera appelé!
X Æ A-12
8

emplace_backl'implémentation conforme transmettra les arguments au vector<Object>::value_typeconstructeur lorsqu'il sera ajouté au vecteur. Je me souviens que Visual Studio ne prenait pas en charge les modèles variadic, mais avec les modèles variadic seront pris en charge dans Visual Studio 2013 RC, donc je suppose qu'une signature conforme sera ajoutée.

Avec emplace_back, si vous transférez les arguments directement au vector<Object>::value_typeconstructeur, vous n'avez pas besoin qu'un type soit mobile ou copiable pour la emplace_backfonction, à proprement parler. Dans le vector<NonCopyableNonMovableObject>cas, cela n'est pas utile, car il a vector<Object>::value_type besoin d'un type copiable ou mobile pour se développer.

Mais notez que cela pourrait être utile car std::map<Key, NonCopyableNonMovableObject>, une fois que vous allouez une entrée dans la carte, elle n'a plus besoin d'être déplacée ou copiée, contrairement à vector, ce qui signifie que vous pouvez utiliser std::mapefficacement avec un type mappé qui n'est ni copiable ni mobile.

Germán Diago
la source
8

Un de plus en cas de listes:

// 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
la source
1

Cas d'utilisation spécifique pour emplace_back: Si vous devez créer un objet temporaire qui sera ensuite poussé dans un conteneur, utilisez à la emplace_backplace de push_back. Il créera l'objet en place dans le conteneur.

Remarques:

  1. push_backdans le cas ci-dessus va créer un objet temporaire et le déplacer dans le conteneur. Cependant, la construction sur place utilisée emplace_backserait plus performante que la construction puis le déplacement de l'objet (ce qui implique généralement une copie).
  2. En général, vous pouvez utiliser emplace_backau lieu de push_backdans tous les cas sans trop de problème. (Voir exceptions )
vaibhav kumar
la source