С++ 11 emplace_back на вектор <struct>?
Рассмотрим следующую программу:
#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");
}
Не работает:
$ 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
Каков правильный способ сделать это и почему?
(Также проверены одиночные и двойные фигурные скобки)
Ответы
Ответ 1
Синтаксис {}
можно использовать для инициализации нового элемента:
V.emplace_back(T{42, 3.14, "foo"});
Это может быть оптимизировано или не оптимизировано, но оно должно быть.
Вам нужно определить конструктор для этого, обратите внимание, что с вашим кодом вы даже не можете сделать:
T a(42, 3.14, "foo");
Но это то, что вам нужно для работы с emplace.
так просто:
struct T {
...
T(int a_, double b_, string c_) a(a_), b(b_), c(c_) {}
}
заставит его работать желаемым образом.
Ответ 2
Вам нужно явно определить ctor для класса:
#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(c) {}
};
vector<T> V;
int main()
{
V.emplace_back(42, 3.14, "foo");
}
Точка использования emplace_back
заключается в том, чтобы избежать создания временного объекта, который затем копируется (или перемещается) в пункт назначения. В то время как также возможно создать временный объект, а затем передать это emplace_back
, он побеждает (по крайней мере, большую часть) цели. То, что вы хотите сделать, это передать отдельные аргументы, а затем emplace_back
вызвать ctor с этими аргументами для создания объекта на месте.
Ответ 3
Конечно, это не ответ, но он показывает интересную особенность кортежей:
#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");
}
Ответ 4
Это, как представляется, рассматривается в 23.2.1/13.
Во-первых, определения:
Учитывая тип контейнера X, имеющий тип allocator_type, идентичный A и value_type, идентичный T и заданный lvalue m типа A, указатель p типа T *, выражение v типа T и rvalue типа T, определяются следующие термины.
Теперь, что делает его конструктивным:
T является EmplaceConstructible в X из args, для нуля или более аргументы args, означает, что следующее выражение хорошо сформировано: allocator_traits:: construct (m, p, args);
И, наконец, примечание о реализации конструктора по умолчанию:
Примечание. Контейнер вызывает allocator_traits:: construct (m, p, args) для построить элемент в точке p с помощью args. Конструкция по умолчанию в std:: allocator вызовет:: new ((void *) p) T (args), но специализированный распределители могут выбирать другое определение.
Это в значительной степени говорит нам о том, что для схемы распределения по умолчанию (и, возможно, единственного) вы должны были определить конструктор с соответствующим количеством аргументов для того, что вы пытаетесь создать-конструируете в контейнер.
Ответ 5
Если вы не хотите (или не можете) добавить конструктор, специализируйте распределитель для T (или создайте свой собственный распределитель).
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)... }; }
};
}
Примечание. Конструкция функции Member, показанная выше, не может компилироваться с clang 3.1 (Извините, я не знаю почему).
Попробуйте следующий, если вы будете использовать clang 3.1 (или другие причины).
void construct(T* p, int a, double b, const string& c) { ::new(static_cast<void*>(p)) T{ a, b, c }; }
Ответ 6
вам нужно определить конструктор для вашего типа T
, потому что он содержит std::string
, который не является тривиальным.
Кроме того, было бы лучше определить (возможно, по умолчанию) move ctor/assign (потому что у вас есть подвижный std::string
как член) - это поможет переместить ваш T
гораздо эффективнее...
или просто используйте T{...}
для вызова перегруженного emplace_back()
, как рекомендовано в ответе на соседние запросы... все зависит от ваших типичных случаев использования...