Рассмотрим следующую программу:
#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
Как правильно это сделать и почему?
(Также пробовали одинарные и двойные скобки)
T t{42,3.14, "foo"}
?Ответы:
Для всех в будущем это поведение будет изменено в C ++ 20 .
Другими словами, даже если внутренняя реализация по-прежнему будет вызывать,
T(arg0, arg1, ...)
она будет рассматриваться как обычнаяT{arg0, arg1, ...}
, как и следовало ожидать.источник
Вам нужно явно определить 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(std::move(c)) {} }; vector<T> V; int main() { V.emplace_back(42, 3.14, "foo"); }
Смысл использования
emplace_back
- избежать создания временного объекта, который затем копируется (или перемещается) в место назначения. Хотя также возможно создать временный объект, а затем передать егоemplace_back
, это нарушает (по крайней мере, большую часть) цель. Что вы хотите сделать, так это передать отдельные аргументы, а затем позволитьemplace_back
вызвать ctor с этими аргументами для создания объекта на месте.источник
T(int a, double b, string c) : a(a), b(b), c(std::move(c))
emplace_back
. Это правильный ответ. Вот какemplace*
работает. Они создают элемент на месте, используя перенаправленные аргументы. Следовательно, для получения указанных аргументов необходим конструктор.c
его,&&
если ничего не сделано с его возможной ценностью; в инициализаторе члена аргумент снова обрабатывается как lvalue в отсутствие приведения, поэтому член просто создается копией. Даже если член был сконструирован с перемещением, не идиоматично требовать от вызывающих всегда передавать временное илиstd::move()
d lvalue (хотя я признаюсь, что в моем коде есть несколько угловых случаев, когда я это делаю, но только в деталях реализации) .Конечно, это не ответ, но он показывает интересную особенность кортежей:
#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"); }
источник
tuple
вместо определения структуры POD, вы получите конструктор бесплатно , что означает, что вы получаетеemplace
синтаксис бесплатно (среди прочего - вы также получаете лексикографический порядок). Вы теряете имена участников, но иногда создавать аксессоры становится меньше, чем все остальное, что вам может понадобиться. Я согласен с тем, что ответ Джерри Коффина намного лучше принятого. Я тоже проголосовал за него много лет назад.pair
... но иногда задаюсь вопросом, действительно ли я много выиграю в чистом выражении, хех. Но, возможноtuple
, последует в будущем. Спасибо за расширение!Если вы не хотите (или не можете) добавлять конструктор, специализируйте распределитель для 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)... }; } }; }
Примечание. Показанная выше конструкция функции-члена не может быть скомпилирована с помощью 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 }; }
источник
std::aligned_storage
Кажется, это описано в 23.2.1 / 13.
Во-первых, определения:
Теперь, что делает его возводимым на место:
И, наконец, примечание о реализации вызова конструкции по умолчанию:
Это в значительной степени говорит нам о том, что для схемы распределителя по умолчанию (и, возможно, единственной) вы должны определить конструктор с нужным количеством аргументов для того, что вы пытаетесь поместить-конструкцию в контейнер.
источник
вам необходимо определить конструктор для вашего типа,
T
потому что он содержитstd::string
нетривиальный.кроме того, было бы лучше определить (возможно, по умолчанию) move ctor / assign (потому что у вас есть подвижный
std::string
элемент в качестве члена) - это поможет переместить вашT
гораздо более эффективный ...или просто используйте
T{...}
для вызова overloaded,emplace_back()
как рекомендовано в ответе Neighboug ... все зависит от ваших типичных вариантов использования ...источник
emplace_back()
вызова при вызове :)Вы можете создать
struct T
экземпляр, а затем переместить его в вектор:V.push_back(std::move(T {42, 3.14, "foo"}));
источник
Вы можете использовать
{}
синтаксис для инициализации нового элемента:V.emplace_back(T{42, 3.14, "foo"});
Это может или не может быть оптимизировано, но это должно быть.
Вы должны определить конструктор, чтобы это работало, обратите внимание, что с вашим кодом вы даже не можете:
T a(42, 3.14, "foo");
Но это то, что вам нужно для работы на месте.
так что просто:
struct T { ... T(int a_, double b_, string c_) a(a_), b(b_), c(c_) {} }
заставит его работать желаемым образом.
источник
std::move
не обязательно.T{42, 3.14, "foo"}
будет уже перенаправлен emplace_back и привязан к конструктору перемещения структуры как rvalue. Однако я бы предпочел решение, которое строит его на месте.