C++11 emplace обратно на вектор?


рассмотрим следующую программу:

#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

Как правильно это сделать и почему?

(также пробовал одинарные и двойные скобки)

6 59

6 ответов:

можно использовать {} синтаксис для инициализации нового элемента:

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_) {}
}

будет работать желаемым образом.

вы должны явно определить конструктор для класса:

#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 с этими аргументами, чтобы создать объект на месте.

конечно, это не ответ, но показывает интересную особенность кортежей:

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

Если вы не хотите (или не можете) добавить конструктор, специализируйте распределитель для 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 }; }

Это, кажется, рассматривается в 23.2.1 / 13.

во-первых, определения:

данный тип контейнера х имея allocator_type идентичны и типа value_type идентичны T и учитывая, именующее м типа а, указатель п типа T*, выражение в типа T, и R-значения РВ типа Т, определены следующие термины.

теперь, что делает его emplace-конструктивным:

T является EmplaceConstructible в X от аргументы , ноль или больше аргументы args, означает, что следующее выражение хорошо сформировано: allocator_traits:: construct(m, p, args);

и, наконец, примечание о реализации по умолчанию вызова конструкции:

Примечание: контейнер вызывает allocator_traits:: construct (m, p, args) to построить элемент в точке p с помощью args. Конструкция по умолчанию в с std::распределитель вызовов ::новая((недействительными*)Р) Т(аргументы), но специализированные распределители могут выбрать различное определение.

Это в значительной степени говорит нам, что для схемы распределителя по умолчанию (и потенциально единственной) вы должны определили конструктор с правильным количеством аргументов для вещи, которую вы пытаетесь emplace-построить в контейнер.

вы должны определить конструктор для вашего типа T потому что он содержит std::string что не тривиально.

кроме того, было бы лучше определить (возможно по умолчанию) перемещение ctor/assign (потому что у вас есть подвижный std::string как член) - это поможет продвинуть ваш T гораздо эффективнее...

или, просто использовать T{...} для вызова перегруженных emplace_back() как рекомендовано в ответе соседа... все зависит от ваших типичных случаев использования...