Considérez le programme suivant:
#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");
}
Ça ne marche pas:
$ 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
Quelle est la bonne façon de procéder et pourquoi?
(Également essayé des accolades simples et doubles)
T t{42,3.14, "foo"}
?Réponses:
Pour toute personne du futur, ce comportement sera modifié dans C ++ 20 .
En d'autres termes, même si l'implémentation en interne s'appellera toujours,
T(arg0, arg1, ...)
elle sera considérée comme régulièreT{arg0, arg1, ...}
que vous attendez.la source
Vous devez définir explicitement un cteur pour la 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"); }
Le but de l'utilisation
emplace_back
est d'éviter de créer un objet temporaire, qui est ensuite copié (ou déplacé) vers la destination. Bien qu'il soit également possible de créer un objet temporaire, puis de le transmettreemplace_back
, cela va à l'encontre (au moins la plupart) de l'objectif. Ce que vous voulez faire est de passer des arguments individuels, puis laissezemplace_back
invoquer le ctor avec ces arguments pour créer l'objet sur place.la source
T(int a, double b, string c) : a(a), b(b), c(std::move(c))
emplace_back
. C'est la bonne réponse. Voilà commentemplace*
travailler. Ils construisent l'élément sur place à l'aide des arguments transmis. Par conséquent, un constructeur est nécessaire pour prendre lesdits arguments.c
en&&
si rien ne se fait avec son possible rvalueness; au moment de l'initialisation du membre, l'argument est à nouveau traité comme une valeur l, en l'absence de conversion, de sorte que le membre est simplement construit par copie. Même si le membre a été construit par déplacement, il n'est pas idiomatique d'exiger des appelants qu'ils transmettent toujours une valeur temporaire oustd::move()
d (bien que j'avoue que j'ai quelques cas de coin dans mon code où je fais cela, mais seulement dans les détails de mise en œuvre) .Bien sûr, ce n'est pas une réponse, mais cela montre une fonctionnalité intéressante des tuples:
#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"); }
la source
tuple
au lieu de définir une structure POD, vous obtenez un constructeur gratuitement , ce qui signifie que vous obtenez laemplace
syntaxe gratuitement (entre autres, vous obtenez également un ordre lexicographique). Vous perdez les noms de membres, mais parfois c'est moins gênant de créer des accesseurs que tout le reste du passe-partout dont vous auriez autrement besoin. Je conviens que la réponse de Jerry Coffin est bien meilleure que celle acceptée. Je l'ai également voté il y a des années.pair
... mais parfois je me demande si je gagne vraiment beaucoup en termes nets, hein. Mais peuttuple
- être suivra-t-il dans le futur. Merci pour l'expansion!Si vous ne voulez pas (ou ne pouvez pas) ajouter de constructeur, spécialisez l'allocateur pour T (ou créez votre propre allocateur).
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)... }; } }; }
Remarque: La construction de fonction membre illustrée ci-dessus ne peut pas être compilée avec clang 3.1 (Désolé, je ne sais pas pourquoi) Essayez le suivant si vous utilisez clang 3.1 (ou d'autres raisons).
void construct(T* p, int a, double b, const string& c) { ::new(static_cast<void*>(p)) T{ a, b, c }; }
la source
std::aligned_storage
Cela semble être couvert dans le 23.2.1 / 13.
Premièrement, les définitions:
Maintenant, qu'est-ce qui le rend constructible en place:
Et enfin une note sur l'implémentation par défaut de l'appel de construction:
Cela nous indique à peu près que pour un schéma d'allocateur par défaut (et potentiellement le seul), vous devez avoir défini un constructeur avec le nombre approprié d'arguments pour l'élément que vous essayez de mettre en place dans un conteneur.
la source
vous devez définir un constructeur pour votre type
T
car il contient un constructeurstd::string
qui n'est pas trivial.de plus, il serait préférable de définir (éventuellement par défaut) move ctor / assign (car vous avez un mobile
std::string
comme membre) - cela aiderait à déplacer votreT
bien plus efficace ...ou, utilisez simplement
T{...}
pour appeler surchargéemplace_back()
comme recommandé dans la réponse de neighboug ... tout dépend de vos cas d'utilisation typiques ...la source
emplace_back()
appel :)Vous pouvez créer l'
struct T
instance, puis la déplacer vers le vecteur:V.push_back(std::move(T {42, 3.14, "foo"}));
la source
Vous pouvez utiliser la
{}
syntaxe pour initialiser le nouvel élément:V.emplace_back(T{42, 3.14, "foo"});
Cela peut être optimisé ou non, mais cela devrait l'être.
Vous devez définir un constructeur pour que cela fonctionne, notez qu'avec votre code, vous ne pouvez même pas faire:
T a(42, 3.14, "foo");
Mais c'est ce dont vous avez besoin pour travailler sur place.
donc juste:
struct T { ... T(int a_, double b_, string c_) a(a_), b(b_), c(c_) {} }
le fera fonctionner de la manière souhaitée.
la source
std::move
n'est pas nécessaire.T{42, 3.14, "foo"}
sera déjà transmis par emplace_back et lié au constructeur struct move en tant que rvalue. Cependant, je préférerais une solution qui la construit sur place.