Конструкция, подобная размещению для 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;

Однако, помимо того, что это уродливо и неэффективно, это не общее решение, поскольку многие объекты могут не предлагать постконструкционные мутации, эквивалентные тому, что вы могли бы получить, вызвав соответствующий конструктор.

Есть ли способ добиться желаемого поведения при построении вектора?


person BeeOnRope    schedule 13.10.2017    source источник
comment
Серьезно, я бы просто использовал std::deque. Но если вы не можете, единственный способ сделать то, что вы хотите, - использовать специальный распределитель.   -  person Brian Bi    schedule 13.10.2017
comment
@Brian - std::deque разрешает эту строительную идиому?   -  person BeeOnRope    schedule 13.10.2017
comment
С std::deque вам придется размещать элементы один за другим, но это будет работать, потому что добавление элементов в начало или конец не перемещает никакие другие элементы.   -  person Brian Bi    schedule 13.10.2017
comment
@Brian - да, в этом отношении по крайней мере std::vector лучше, к сожалению, иногда мне нужна гарантия непрерывного хранилища, которую предлагает vector. Меня также интересует подход с использованием настраиваемого распределителя: мне не очевидно, как настраиваемый распределитель обходит интерфейс конструктора vector.   -  person BeeOnRope    schedule 13.10.2017
comment
Возможный дубликат Как размещать элементы при построении std :: vector?   -  person Chris Drew    schedule 14.10.2017
comment
Мне кажется, это тот же вопрос, что и stackoverflow.com/questions/13193484/, немного перемешав слова   -  person M.M    schedule 14.10.2017
comment
@ M.M - это немного по-другому. OP там хотел push_back элементов в вектор, что никогда не будет разрешено для неподвижных типов, поскольку существует потенциальное изменение размера, которое требует подвижности. Однако идея размещения объектов непосредственно во вновь созданном векторе может работать как в теории, так и на практике, поскольку последняя часть ответа ниже показывает использование конструктора, который принимает два итератора.   -  person BeeOnRope    schedule 14.10.2017


Ответы (1)


Общее решение - заставить ваш вектор использовать специальный распределитель, construct метод которого выполняет соответствующую инициализацию. В приведенном ниже коде v использует MyAllocator<NonMovable> распределитель, а не std::allocator<NonMovable>. Когда метод construct вызывается без аргументов, он фактически вызывает конструктор с соответствующим аргументом. Таким образом, конструктор по умолчанию может правильно инициализировать элементы.

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

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

person Brian Bi    schedule 13.10.2017