C ++ 11 emplace_back sur le vecteur <struct>?

87

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)

Andrew Tomazos
la source
4
Cela fonctionnera si vous fournissez un constructeur approprié.
chris
2
Existe-t-il un moyen de le construire sur place avec le constructeur de structure d'accolade créé automatiquement utilisé par T t{42,3.14, "foo"}?
Andrew Tomazos
4
Je ne pense pas que cela prenne la forme d'un constructeur. C'est l'initialisation globale.
chris
5
Je n'essaye pas d'affecter votre opinion de quelque manière que ce soit .. Mais au cas où vous n'auriez pas prêté attention à une question mince depuis un certain temps .. La réponse acceptée, avec le plein respect de son auteur, n'est pas du tout une réponse à votre question et peut induire les lecteurs en erreur.
Humam Helfawi

Réponses:

18

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ère T{arg0, arg1, ...}que vous attendez.

Rouge XIII
la source
93

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_backest 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 transmettre emplace_back, cela va à l'encontre (au moins la plupart) de l'objectif. Ce que vous voulez faire est de passer des arguments individuels, puis laissez emplace_backinvoquer le ctor avec ces arguments pour créer l'objet sur place.

Jerry Coffin
la source
12
Je pense que la meilleure façon serait d'écrireT(int a, double b, string c) : a(a), b(b), c(std::move(c))
balki
9
La réponse acceptée va à l'encontre du but de emplace_back. C'est la bonne réponse. Voilà comment emplace*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.
underscore_d
1
encore, vector pourrait fournir un emplace_aggr, non?
tamas.kenez
@balki droit, il n'y a pas de point de prendre cen &&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 ou std::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) .
underscore_d
25

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");
}
rici
la source
8
Ce n'est certainement pas une réponse. qu'est-ce qui est si intéressant? tout type avec un ctor peut être placé de cette manière. tuple a un ctor. la structure de l'op ne l'a pas fait. c'est la réponse.
underscore_d
6
@underscore_d: Je ne suis pas sûr de me souvenir de tous les détails de ce à quoi je pensais il y a 3 ans et demi, mais je pense que ce que je suggérais était que si vous utilisez simplement un tupleau lieu de définir une structure POD, vous obtenez un constructeur gratuitement , ce qui signifie que vous obtenez la emplacesyntaxe 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.
rici
3
Ouais, l'épeler m'aide à comprendre ce que tu voulais dire! Bon point. Je suis d'accord que parfois la généralisation est supportable quand on la compare aux autres trucs que la STL nous fournit: je l'utilise semi-souvent avec pair... mais parfois je me demande si je gagne vraiment beaucoup en termes nets, hein. Mais peut tuple- être suivra-t-il dans le futur. Merci pour l'expansion!
underscore_d
12

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 }; }
Mitsuru Kariya
la source
Dans votre fonction d'allocation, n'avez-vous pas à vous soucier de l'alignement? Voirstd::aligned_storage
Andrew Tomazos
Aucun problème. Selon les spécifications, les effets de "void * :: operator new (size_t size)" sont "La fonction d'allocation appelée par une nouvelle expression pour allouer des octets de taille de stockage correctement alignés pour représenter tout objet de cette taille."
Mitsuru Kariya
6

Cela semble être couvert dans le 23.2.1 / 13.

Premièrement, les définitions:

Etant donné un conteneur de type X ayant un allocator_type identique à A et un value_type identique à T et étant donné une lvalue m de type A, un pointeur p de type T *, une expression v de type T, et une rvalue rv de type T, le les termes suivants sont définis.

Maintenant, qu'est-ce qui le rend constructible en place:

T est EmplaceConstructible en X à partir d'arguments, pour zéro ou plusieurs arguments args, signifie que l'expression suivante est bien formée: allocator_traits :: construct (m, p, args);

Et enfin une note sur l'implémentation par défaut de l'appel de construction:

Remarque: Un conteneur appelle allocator_traits :: construct (m, p, args) pour construire un élément en p en utilisant args. La construction par défaut dans std :: allocator appellera :: new ((void *) p) T (args), mais les allocateurs spécialisés peuvent choisir une définition différente.

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.

Marque B
la source
-2

vous devez définir un constructeur pour votre type Tcar il contient un constructeur std::stringqui 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::stringcomme membre) - cela aiderait à déplacer votre Tbien 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 ...

zaufi
la source
Un constructeur de mouvement est automatiquement généré pour T
Andrew Tomazos
1
@ AndrewTomazos-Fathomling: uniquement si aucun critère utilisateur n'est défini
zaufi
1
Correct, et ils ne le sont pas.
Andrew Tomazos du
@ AndrewTomazos-Fathomling: mais vous devez en définir, pour éviter une instance temporaire sur emplace_back()appel :)
zaufi
1
En fait incorrect. Un constructeur de déplacement est automatiquement généré à condition qu'aucun destructeur, constructeur de copie ou opérateur d'affectation ne soit défini. La définition d'un constructeur membre à 3 arguments à utiliser avec emplace_back ne supprimera pas le constructeur de déplacement par défaut.
Andrew Tomazos du
-2

Vous pouvez créer l' struct Tinstance, puis la déplacer vers le vecteur:

V.push_back(std::move(T {42, 3.14, "foo"}));
AlexB
la source
2
Vous n'avez pas besoin de std :: move () un objet temporaire T {...}. C'est déjà un objet temporaire (rvalue). Vous pouvez donc simplement supprimer std :: move () de votre exemple.
Nadav Har'El le
De plus, même le nom de type T n'est pas nécessaire - le compilateur peut le deviner. Donc, juste un "V.push_back {42, 3.14," foo "}" fonctionnera.
Nadav Har'El le
-8

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.

perréel
la source
10
Est-ce que cela va construire un temporaire puis le déplacer dans le tableau? - ou construira-t-il l'élément sur place?
Andrew Tomazos du
3
Le std::moven'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.
Andrew Tomazos
37
dans ce cas, le mouvement est presque exactement équivalent à la copie, de sorte que tout le point de mise en place est manqué.
Alex I.
5
@AlexI. En effet! Cette syntaxe crée un temporaire, qui est passé comme argument à 'emplace_back'. Manque complètement le point.
aldo
5
Je ne comprends pas tous les commentaires négatifs. Le compilateur n'utilisera-t-il pas RVO dans ce cas?
Euri Pinhollow