Эмплозоподобная конструкция для 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());

(Хотя, если вы не можете позволить себе дополнительную память, вам придется написать собственный итератор, который будет намного больше кода.)

Другие вопросы по тегам