Rozważ następujący program:
#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");
}
To nie działa:
$ 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
Jaki jest właściwy sposób, aby to zrobić i dlaczego?
(Wypróbowałem także pojedyncze i podwójne szelki)
T t{42,3.14, "foo"}
?Odpowiedzi:
Dla każdego z przyszłości to zachowanie zostanie zmienione w C ++ 20 .
Innymi słowy, nawet jeśli implementacja wewnętrzna nadal będzie nazywać
T(arg0, arg1, ...)
to, zostanie uznana za normalnąT{arg0, arg1, ...}
, której można się spodziewać.źródło
Musisz jawnie zdefiniować ctora dla klasy:
#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"); }
Celem używania
emplace_back
jest uniknięcie tworzenia tymczasowego obiektu, który jest następnie kopiowany (lub przenoszony) do miejsca docelowego. Chociaż możliwe jest również utworzenie tymczasowego obiektu, a następnie przekazanieemplace_back
go, pokonuje on (przynajmniej większość) cel. To, co chcesz zrobić, to przekazać poszczególne argumenty, a następnieemplace_back
wywołać ctora z tymi argumentami, aby utworzyć obiekt w miejscu.źródło
T(int a, double b, string c) : a(a), b(b), c(std::move(c))
emplace_back
. To jest poprawna odpowiedź. Takemplace*
działa. Konstruują element na miejscu, używając przekazanych argumentów. Stąd potrzebny jest konstruktor, aby wziąć te argumenty.c
się,&&
jeśli nic nie zostanie zrobione z jego możliwą wartością r; przy inicjalizatorze składowej argument jest ponownie traktowany jako lwartość, w przypadku braku rzutowania, więc element członkowski jest po prostu konstruowany przez kopiowanie. Nawet jeśli element członkowski został skonstruowany jako ruch, nie jest idiomatyczne wymaganie, aby wywołujący zawsze przekazywali tymczasową lubstd::move()
d lwartość (chociaż przyznam się, że mam kilka przypadków narożnych w moim kodzie, w których to robię, ale tylko w szczegółach implementacji) .Oczywiście nie jest to odpowiedź, ale pokazuje interesującą cechę krotek:
#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"); }
źródło
tuple
zamiast definiowania struktury POD, otrzymasz konstruktora za darmo co oznacza, że otrzymujeszemplace
składnię za darmo (między innymi - otrzymujesz też porządek leksykograficzny). Tracisz nazwy członków, ale czasami tworzenie akcesorów jest mniej kłopotliwe niż cała reszta standardowego szablonu, którego w innym przypadku potrzebowałbyś. Zgadzam się, że odpowiedź Jerry'ego Coffina jest znacznie lepsza niż ta przyjęta. Głosowałem też za tym lata temu.pair
... ale czasami zastanawiam się, czy naprawdę dużo zyskam w ujęciu netto, heh. Ale być możetuple
nastąpi to w przyszłości. Dzięki za rozszerzenie!Jeśli nie chcesz (lub nie możesz) dodawać konstruktora, wyspecjalizuj alokator dla T (lub utwórz własny alokator).
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)... }; } }; }
Uwaga: Konstrukcja funkcji składowej pokazana powyżej nie może skompilować się z clang 3.1 (przepraszam, nie wiem dlaczego). Wypróbuj następny, jeśli będziesz używać clang 3.1 (lub z innych powodów).
void construct(T* p, int a, double b, const string& c) { ::new(static_cast<void*>(p)) T{ a, b, c }; }
źródło
std::aligned_storage
Wydaje się, że jest to uwzględnione w 23.2.1 / 13.
Po pierwsze, definicje:
Teraz, co sprawia, że jest to możliwe do zbudowania:
I na koniec uwaga na temat domyślnej implementacji wywołania konstrukcji:
To prawie mówi nam, że dla domyślnego (i potencjalnie jedynego) schematu alokatora musisz zdefiniować konstruktor z odpowiednią liczbą argumentów dla rzeczy, którą próbujesz umieścić w kontenerze.
źródło
musisz zdefiniować konstruktora dla swojego typu,
T
ponieważ zawiera on plikstd::string
który nie jest trywialny.co więcej, byłoby lepiej zdefiniować (możliwe domyślne) przeniesienie ctor / assign (ponieważ masz ruchomy
std::string
jako członek) - pomogłoby to przenieśćT
znacznie wydajniej ...lub po prostu użyj
T{...}
do wywołania przeciążonego,emplace_back()
zgodnie z zaleceniami w odpowiedzi sąsiada ... wszystko zależy od typowych przypadków użycia ...źródło
emplace_back()
wezwania :)Możesz utworzyć
struct T
instancję, a następnie przenieść ją do wektora:V.push_back(std::move(T {42, 3.14, "foo"}));
źródło
Możesz użyć
{}
składni, aby zainicjować nowy element:V.emplace_back(T{42, 3.14, "foo"});
Może to być zoptymalizowane lub nie, ale powinno być.
Musisz zdefiniować konstruktora, aby to działało, pamiętaj, że z twoim kodem nie możesz nawet zrobić:
T a(42, 3.14, "foo");
Ale to jest to, czego potrzebujesz, aby mieć pracę w miejscu pracy.
więc tylko:
struct T { ... T(int a_, double b_, string c_) a(a_), b(b_), c(c_) {} }
sprawi, że będzie działać w pożądany sposób.
źródło
std::move
jest to konieczne.T{42, 3.14, "foo"}
zostanie już przekazana przez embrace_back i powiązana z konstruktorem przenoszenia struktury jako rvalue. Wolałbym jednak rozwiązanie, które konstruuje je na miejscu.