114 votes

C++11 emplace_back sur vector<struct> ?

Considérons 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 ?

(J'ai aussi essayé les accolades simples et doubles)

115voto

Jerry Coffin Points 237758

Vous devez définir explicitement un ctor 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");
}

L'intérêt d'utiliser 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 passer à la commande emplace_back cela va à l'encontre (du moins en grande partie) du but recherché. Ce que vous voulez faire, c'est passer des arguments individuels, puis laisser la fonction emplace_back invoquez le ctor avec ces arguments pour créer l'objet sur place.

44voto

Red XIII Points 1247

Pour quiconque vient du futur, ce comportement sera modifié en C++20 .

En d'autres termes, même si l'implémentation interne appelle toujours T(arg0, arg1, ...) il sera considéré comme régulier T{arg0, arg1, ...} que l'on pourrait attendre.

28voto

rici Points 45980

Bien sûr, ce n'est pas une réponse, mais cela montre une caractéristique 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");
}

13voto

Mitsuru Kariya Points 373

Si vous ne voulez pas (ou ne pouvez pas) ajouter un constructeur, spécialisez un 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)... }; }
    };
}

Note : La construction de la fonction membre montrée ci-dessus ne peut pas compiler avec clang 3.1 (Désolé, je ne sais pas pourquoi). Essayez la suivante si vous utilisez clang 3.1 (ou pour d'autres raisons).

void construct(T* p, int a, double b, const string& c) { ::new(static_cast<void*>(p)) T{ a, b, c }; }

6voto

perreal Points 47912

Vous pouvez utiliser le {} pour initialiser le nouvel élément :

V.emplace_back(T{42, 3.14, "foo"});

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X