C ++ 11 emplace_back sul vettore <struct>?


89

Considera il seguente programma:

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

Non funziona:

$ 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

Qual è il modo corretto per farlo e perché?

(Ho provato anche parentesi graffe singole e doppie)


4
Funzionerà se fornisci un costruttore appropriato.
chris

3
C'è un modo per costruirlo sul posto con il costruttore di strutture di parentesi graffe creato automaticamente usato da T t{42,3.14, "foo"}?
Andrew Tomazos

4
Non credo che prenda la forma di un costruttore. È un'inizializzazione aggregata.
chris


5
Non sto cercando di influenzare in alcun modo la tua opinione .. Ma nel caso tu non abbia prestato attenzione alla domanda sottile da tempo .. La risposta accettata, nel pieno rispetto di chi l'ha scritta, non è affatto una risposta alla tua domanda e può fuorviare i lettori.
Humam Helfawi

Risposte:


19

Per chiunque dal futuro, questo comportamento verrà modificato in C ++ 20 .

In altre parole, anche se l'implementazione interna lo chiamerà ancora T(arg0, arg1, ...), sarà considerato regolare T{arg0, arg1, ...}come ci si aspetterebbe.


97

Devi definire esplicitamente un ctor per 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");
}

Lo scopo dell'utilizzo emplace_backè evitare di creare un oggetto temporaneo, che viene poi copiato (o spostato) nella destinazione. Sebbene sia anche possibile creare un oggetto temporaneo, quindi passarlo a emplace_back, vanifica (almeno la maggior parte) lo scopo. Quello che vuoi fare è passare singoli argomenti, quindi emplace_backinvoca il ctor con quegli argomenti per creare l'oggetto al suo posto.


12
Penso che il modo migliore sarebbe scrivereT(int a, double b, string c) : a(a), b(b), c(std::move(c))
balki

9
La risposta accettata sconfigge lo scopo di emplace_back. Questa è la risposta corretta. Ecco come emplace*funziona. Costruiscono l'elemento sul posto utilizzando gli argomenti inoltrati. Quindi, è necessario un costruttore per prendere detti argomenti.
underscore_d

1
tuttavia, il vettore potrebbe fornire un emplace_aggr, giusto?
tamas.kenez

@balki Giusto, non c'è motivo di prendere cper &&se non si interviene con il suo possibile rvalueness; All'inizializzazione del membro, l'argomento viene trattato di nuovo come un valore, in assenza di cast, quindi il membro viene semplicemente costruito in copia. Anche se il membro è stato costruito con lo spostamento, non è idiomatico richiedere ai chiamanti di passare sempre un valore temporaneo o std::move()d l (anche se confesserò che ho alcuni casi d'angolo nel mio codice in cui lo faccio, ma solo nei dettagli di implementazione) .
underscore_d

26

Naturalmente, questa non è una risposta, ma mostra una caratteristica interessante delle tuple:

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

9
Non è certo una risposta. cosa c'è di così interessante? qualsiasi tipo con un ctor può essere posizionato in questo modo. tuple ha un ctor. la struttura dell'operazione no. questa è la risposta.
underscore_d

6
@underscore_d: Non sono sicuro di ricordare ogni dettaglio di quello che stavo pensando 3 anni e mezzo fa, ma credo che quello che stavo suggerendo era che se usi semplicemente a tupleinvece di definire una struttura POD, allora ottieni un costruttore gratuitamente , il che significa che ottieni la emplacesintassi gratuitamente (tra le altre cose - ottieni anche l'ordinamento lessicografico). Perdi i nomi dei membri, ma a volte è meno fastidioso creare funzioni di accesso rispetto a tutto il resto del boilerplate di cui avresti altrimenti bisogno. Sono d'accordo che la risposta di Jerry Coffin sia molto migliore di quella accettata. L'ho anche votato al rialzo anni fa.
rici

3
Sì, scriverlo mi aiuta a capire cosa intendevi! Buon punto. Sono d'accordo sul fatto che a volte la generalizzazione sia sopportabile se confrontata con le altre cose che ci offre STL: lo uso semi-spesso con pair... ma a volte mi chiedo se guadagno davvero molto in termini netti, eh. Ma forse tupleseguirà in futuro. Grazie per l'espansione!
underscore_d

12

Se non vuoi (o non puoi) aggiungere un costruttore, specializza l'allocatore per T (o crea il tuo allocatore).

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

Nota: il costrutto della funzione membro mostrato sopra non può essere compilato con clang 3.1 (mi dispiace, non so perché). Prova il prossimo se utilizzerai clang 3.1 (o altri motivi).

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

Nella tua funzione di allocazione, non devi preoccuparti dell'allineamento? Vedistd::aligned_storage
Andrew Tomazos

Nessun problema. Secondo le specifiche, gli effetti di "void * :: operator new (size_t size)" sono "La funzione di allocazione chiamata da una nuova espressione per allocare size byte di memoria adeguatamente allineati per rappresentare qualsiasi oggetto di quella dimensione."
Mitsuru Kariya

6

Questo sembra essere trattato in 23.2.1 / 13.

Innanzitutto, definizioni:

Dato un tipo di contenitore X con allocator_type identico ad A e value_type identico a T e dato un lvalue m di tipo A, un puntatore p di tipo T *, un'espressione v di tipo T, e un rvalue rv di tipo T, il vengono definiti i seguenti termini.

Ora, cosa lo rende costruibile in postazione:

T è EmplaceConstructible in X da args, per zero o più argomenti args, significa che la seguente espressione è ben formata: allocator_traits :: construct (m, p, args);

E infine una nota sull'implementazione predefinita della chiamata al costrutto:

Nota: un contenitore chiama allocator_traits :: construct (m, p, args) per costruire un elemento in p utilizzando args. Il costrutto predefinito in std :: allocator chiamerà :: new ((void *) p) T (args), ma gli allocatori specializzati possono scegliere una definizione diversa.

Questo ci dice più o meno che per uno schema allocatore predefinito (e potenzialmente l'unico) è necessario aver definito un costruttore con il numero appropriato di argomenti per l'oggetto che si sta tentando di collocare in un contenitore.


-2

devi definire un costruttore per il tuo tipo Tperché contiene un std::stringche non è banale.

inoltre, sarebbe meglio definire (possibile predefinito) spostare ctor / assign (perché hai un mobile std::stringcome membro) - questo aiuterebbe a spostare il tuo Tmolto più efficiente ...

oppure, usa semplicemente T{...}per chiamare sovraccarico emplace_back()come raccomandato nella risposta di neighboug ... tutto dipende dai tuoi casi d'uso tipici ...


Un costruttore di mosse viene generato automaticamente per T
Andrew Tomazos

1
@ AndrewTomazos-Fathomling: solo se non sono definiti ctor utente
zaufi

1
Corretto, e non lo sono.
Andrew Tomazos

@ AndrewTomazos-Fathomling: ma devi definirne alcuni, per evitare istanze temporanee su emplace_back()chiamata :)
zaufi

1
In realtà non corretto. Un costruttore di spostamenti viene generato automaticamente a condizione che non siano definiti distruttori, costruttori di copie o operatori di assegnazione. La definizione di un costruttore a 3 argomenti a livello di membro da utilizzare con emplace_back non sopprimerà il costruttore di spostamento predefinito.
Andrew Tomazos

-2

Puoi creare l' struct Tistanza e poi spostarla nel vettore:

V.push_back(std::move(T {42, 3.14, "foo"}));

2
Non è necessario std :: move () un oggetto temporaneo T {...}. È già un oggetto temporaneo (rvalue). Quindi puoi semplicemente rilasciare std :: move () dal tuo esempio.
Nadav Har'El

Inoltre, anche il nome del tipo T non è necessario: il compilatore può indovinarlo. Quindi funzionerà solo un "V.push_back {42, 3.14," foo "}".
Nadav Har'El

-8

È possibile utilizzare la {}sintassi per inizializzare il nuovo elemento:

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

Questo può o non può essere ottimizzato, ma dovrebbe esserlo.

Devi definire un costruttore affinché funzioni, nota che con il tuo codice non puoi nemmeno fare:

T a(42, 3.14, "foo");

Ma questo è ciò di cui hai bisogno per avere un posto di lavoro.

Quindi:

struct T { 
  ...
  T(int a_, double b_, string c_) a(a_), b(b_), c(c_) {}
}

lo farà funzionare nel modo desiderato.


10
Questo costruirà un temporaneo e quindi lo sposterà nell'array? - o costruirà l'oggetto al suo posto?
Andrew Tomazos

3
Non std::moveè necessario. T{42, 3.14, "foo"}sarà già inoltrato da emplace_back e si legherà al costruttore di spostamento della struttura come valore. Tuttavia preferirei una soluzione che lo costruisca sul posto.
Andrew Tomazos

37
in questo caso lo spostamento è quasi esattamente equivalente a copiare, quindi l'intero punto di emplacing viene perso.
Alex I.

5
@AlexI. Infatti! Questa sintassi crea un temporaneo, che viene passato come argomento a 'emplace_back'. Manca completamente il punto.
aldo

5
Non capisco tutti i feedback negativi. Il compilatore non userà RVO in questo caso?
Euri Pinhollow
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.