Эмплозоподобная конструкция для std::vector
Представьте, что я хочу построить фиксированный размер std::vector
объектов без конструкторов перемещения или копирования, таких как std::atomic<int>
, В этом случае основной std::atomic
класс имеет 1-аргументный конструктор, который принимает int
, а также конструктор по умолчанию (который инициализирует значение в 0).
С использованием initializer_list
синтаксис как std::vector<std::atomic<int>> v{1,2,3}
не работает, потому что аргументы сначала преобразуются в тип элемента T
вектора как часть создания initializer_list
и поэтому будет вызван конструктор копирования или перемещения.
В частном случае std::atomic<int>
Я могу создать вектор по умолчанию, а затем мутировать элементы после:
std::vector<std::atomic<int>> v(3);
v[0] = 1;
v[1] = 2;
v[2] = 3;
Однако, помимо того, что он уродлив и неэффективен, он не является общим решением, поскольку многие объекты могут не предлагать мутации после создания, эквивалентные тому, что вы могли бы получить, вызвав соответствующий конструктор.
Есть ли способ получить "emplace-like" поведение, которое я хочу при построении вектора?
1 ответ
Общее решение состоит в том, чтобы заставить ваш вектор использовать специальный распределитель, construct
Метод выполняет соответствующую инициализацию. В приведенном ниже коде v
использует MyAllocator<NonMovable>
распределитель, а не std::allocator<NonMovable>
, Когда construct
Метод вызывается без аргументов, фактически он вызывает конструктор с соответствующим аргументом. Таким образом, конструктор по умолчанию может правильно инициализировать элементы.
(Для упрощения я сделал next_value
static в этом примере, но это может быть и нестатическая переменная-член, которая инициализируется при MyAllocator
построен.)
#include <stdio.h>
#include <memory>
#include <new>
#include <vector>
struct NonMovable {
NonMovable(int x) : x(x) {}
const int x;
};
template <class T>
struct MyAllocator {
typedef T value_type;
static int next_value;
T* allocate(size_t n) {
return static_cast<T*>(::operator new(n * sizeof(T)));
}
void deallocate(T* p, size_t n) {
::operator delete(p);
}
template <class U>
void construct(U* p) {
new (p) U(++next_value);
}
};
template <class T> int MyAllocator<T>::next_value = 0;
int main() {
std::vector<NonMovable, MyAllocator<NonMovable>> v(10);
for (int i = 0; i < 10; i++) {
printf("%d\n", v[i].x);
}
}
http://coliru.stacked-crooked.com/a/1a89fddd325514bf
Это единственно возможное решение, когда вам не разрешено NonMovable
Класс и его конструктор могут требовать нескольких аргументов. В случае, когда вам нужно передать только один аргумент каждому конструктору, есть гораздо более простое решение, которое использует конструктор диапазона std::vector
, вот так:
std::vector<int> ints(10);
std::iota(ints.begin(), ints.end(), 1);
std::vector<NonMovable> v(ints.begin(), ints.end());
(Хотя, если вы не можете позволить себе дополнительную память, вам придется написать собственный итератор, который будет намного больше кода.)