Betrachten Sie das folgende Programm:
#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");
}
Es funktioniert nicht:
$ 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
Was ist der richtige Weg und warum?
(Auch versucht Einzel- und Doppelklammern)
T t{42,3.14, "foo"}
?Antworten:
Für jeden aus der Zukunft wird dieses Verhalten in C ++ 20 geändert .
Mit anderen Worten, obwohl die interne Implementierung immer noch aufgerufen wird
T(arg0, arg1, ...)
, wird sie als regelmäßig angesehenT{arg0, arg1, ...}
, wie Sie es erwarten würden.quelle
Sie müssen explizit einen Ctor für die Klasse definieren:
#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"); }
Der
emplace_back
Zweck der Verwendung besteht darin, zu vermeiden, dass ein temporäres Objekt erstellt wird, das dann an das Ziel kopiert (oder verschoben) wird. Es ist zwar auch möglich, ein temporäres Objekt zu erstellen und dieses dann an zu übergebenemplace_back
, es macht jedoch (zumindest den größten Teil) den Zweck zunichte. Sie möchten einzelne Argumente übergeben und dannemplace_back
den ctor mit diesen Argumenten aufrufen, um das Objekt an Ort und Stelle zu erstellen.quelle
T(int a, double b, string c) : a(a), b(b), c(std::move(c))
emplace_back
. Dies ist die richtige Antwort. Soemplace*
funktioniert es. Sie konstruieren das Element direkt anhand der weitergeleiteten Argumente. Daher wird ein Konstruktor benötigt, um diese Argumente zu übernehmen.c
durch ,&&
wenn nichts mit ihren möglichen rvalueness getan wird; Beim Initialisierer des Mitglieds wird das Argument in Abwesenheit einer Besetzung erneut als Wert behandelt, sodass das Mitglied nur kopiert wird. Selbst wenn das Mitglied verschoben wurde, ist es nicht idiomatisch, von Anrufern zu verlangen, dass sie immer einen temporären oderstd::move()
d-Wert übergeben (obwohl ich zugeben werde, dass ich in meinem Code einige Eckfälle habe, in denen ich das mache, aber nur in Implementierungsdetails). .Natürlich ist dies keine Antwort, aber es zeigt ein interessantes Merkmal von Tupeln:
#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"); }
quelle
tuple
Sie einen Konstruktor kostenlos erhalten , wenn Sie nur eine anstelle einer POD-Struktur verwenden Dies bedeutet, dass Sie dieemplace
Syntax kostenlos erhalten (unter anderem - Sie erhalten auch eine lexikografische Reihenfolge). Sie verlieren Mitgliedsnamen, aber manchmal ist es weniger mühsam, Accessoren zu erstellen als der gesamte Rest der Boilerplate, die Sie sonst benötigen würden. Ich stimme zu, dass Jerry Coffins Antwort viel besser ist als die akzeptierte. Ich habe es auch vor Jahren positiv bewertet.pair
... aber manchmal frage ich mich, ob ich wirklich netto viel gewinne, heh. Aber vielleichttuple
folgt in Zukunft. Vielen Dank für die Erweiterung!Wenn Sie keinen Konstruktor hinzufügen möchten (oder können), spezialisieren Sie den Allokator auf T (oder erstellen Sie Ihren eigenen Allokator).
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)... }; } }; }
Hinweis: Das oben gezeigte Elementfunktionskonstrukt kann nicht mit clang 3.1 kompiliert werden (Entschuldigung, ich weiß nicht warum). Versuchen Sie es als nächstes, wenn Sie Clang 3.1 (oder andere Gründe) verwenden.
void construct(T* p, int a, double b, const string& c) { ::new(static_cast<void*>(p)) T{ a, b, c }; }
quelle
std::aligned_storage
Dies scheint in 23.2.1 / 13 behandelt zu werden.
Erstens Definitionen:
Was macht es nun emplace-konstruierbar:
Und zum Schluss noch ein Hinweis zur Standardimplementierung des Konstruktaufrufs:
Dies sagt uns ziemlich genau, dass Sie für ein Standard- Allokator- Schema (und möglicherweise das einzige Allokator-Schema) einen Konstruktor mit der richtigen Anzahl von Argumenten für das Objekt definiert haben müssen, das Sie in einen Container einfügen möchten.
quelle
Sie müssen einen Konstruktor für Ihren Typ definieren,
T
da dieser einenstd::string
nicht trivialen enthält.Darüber hinaus ist es besser, (möglicherweise standardmäßig) move ctor / assign zu definieren (da Sie ein bewegliches
std::string
Mitglied haben) - dies würde dazu beitragen, IhrT
viel effizienteres zu verschieben ...oder verwenden Sie einfach
T{...}
, um überlastet anzurufen,emplace_back()
wie in der Nachbarantwort empfohlen ... alles hängt von Ihren typischen Anwendungsfällen ab ...quelle
emplace_back()
Abruf zu vermeiden :)Sie können die
struct T
Instanz erstellen und dann in den Vektor verschieben:V.push_back(std::move(T {42, 3.14, "foo"}));
quelle
Mit der
{}
Syntax können Sie das neue Element initialisieren:V.emplace_back(T{42, 3.14, "foo"});
Dies kann optimiert werden oder nicht, sollte es aber sein.
Sie müssen einen Konstruktor definieren, damit dies funktioniert. Beachten Sie, dass Sie mit Ihrem Code nicht einmal Folgendes tun können:
T a(42, 3.14, "foo");
Aber das ist es, was Sie brauchen, um Arbeit zu haben.
also nur:
struct T { ... T(int a_, double b_, string c_) a(a_), b(b_), c(c_) {} }
wird es auf die gewünschte Weise funktionieren lassen.
quelle
std::move
ist nicht nötig.T{42, 3.14, "foo"}
wird bereits von emplace_back weitergeleitet und als r-Wert an den struct move-Konstruktor gebunden. Ich würde jedoch eine Lösung bevorzugen, die sie an Ort und Stelle erstellt.