Существует ли стандартный класс C ++ для массивов с фиксированным размером во время выполнения? - PullRequest
4 голосов
/ 27 марта 2019

Мне нужен контейнер с известным размером во время выполнения без изменения размера.std::unique_ptr<T[]> было бы полезно, но нет инкапсулированного члена размера.В то же время std::array только для размера компиляции.Следовательно, мне нужна некоторая комбинация этих классов без / минимальных накладных расходов.

Есть ли стандартный класс для моих нужд, может быть, что-то в будущем C ++ 20?

Ответы [ 5 ]

11 голосов
/ 27 марта 2019

Используйте std::vector.Это класс для динамического массива в STL.

Позволяет изменить его размер или вставить в него элементы:

auto vec = std::vector<int>{};

vec.resize(10); // now vector has 10 ints 0 initialized
vec.push_back(1); // now 11 ints

Некоторые проблемы, указанные в комментариях:

вектор имеет избыточный интерфейс

Так же std::array.У вас есть более 20 функций в std::array, включая операторов.

Просто не используйте то, что вам не нужно.Вы не платите за функцию, которую не будете использовать.Это даже не увеличит ваш двоичный размер.

вектор заставит инициализировать элементы при изменении размера.Насколько я знаю, нельзя использовать operator[] для индексов> = размер (несмотря на вызов reserve).

Это не так, как это должно быть использовано.При резервировании вы должны изменить размер вектора с помощью resize или вставив в него элементы.Вы говорите, что vector заставит инициализировать элементы в него, но проблема в том, что вы не можете вызвать operator= для неструктурированных объектов, включая целые.

Вот пример использования reseve:

auto vec = std::vector<int>{};

vec.reseve(10); // capacity of at least 10
vec.resize(3); // Contains 3 zero initialized ints.

// If you don't want to `force` initialize elements
// you should push or emplace element into it:

vec.emplace_back(1); // no reallocation for the three operations.
vec.emplace_back(2); // no default initialize either.
vec.emplace_back(3); // ints constructed with arguments in emplace_back

Имейте в виду, что для такого размещения и использования существует высокая вероятность того, что компилятор может полностью исключить создание элементов в векторе.Возможно, в вашем коде нет лишних затрат.

Я бы предложил измерить и profile , если ваш код подвергается очень точной спецификации производительности.Если у вас нет такой спецификации, скорее всего, это преждевременная оптимизация.Затраты на выделение памяти полностью измеряют время, необходимое для инициализации элементов один за другим.

Другие части вашей программы могут подвергаться рефакторингу для получения гораздо большей производительности, чем может дать вам тривиальная инициализация,На самом деле, помешать этому может помешать оптимизации и замедлить работу вашей программы.

5 голосов
/ 27 марта 2019

Выделите память, используя std::unique_ptr<T[]>, как вы предложили, но для ее использования - создайте std::span (в C ++ 20; gsl::span до C ++ 20) из необработанногоуказатель и количество элементов, и передать диапазон (по значению; диапазоны являются ссылочными типами, своего рода).Пролет даст вам все навороты контейнера: размер, итераторы, ранжирование, работы.

#include <span>
// or:
// #include <gsl/span>

int main() {

    // ... etc. ...

    {
        size_t size = 10e5;
        auto uptr { std::make_unique<double[]>(size) };
        do_stuff_with_the_doubles(std::span<int> my_span { uptr.get(), size });
    }

    // ... etc. ...
}

Для получения дополнительной информации о пролетах см .:

Что такое «промежуток» и когда я должен его использовать?

4 голосов
/ 27 марта 2019

Используйте std::vector. Если вы хотите исключить возможность изменения его размера, оберните его.

template <typename T>
single_allocation_vector : private std::vector<T>, public gsl::span<T>
{
    single_allocation_vector(size_t n, T t = {}) : vector(n, t), span(vector::data(), n) {}
    // other constructors to taste
};
3 голосов
/ 27 марта 2019

То, что называется std::dynarray, было предложено для C ++ 14:

std :: dynarray - это контейнер последовательностей, который инкапсулирует массивы с размером, который фиксирован при построении и не изменяется в течение всего времени жизни объекта.

Но было слишком много проблем , и оно не стало частью стандарта.

Таким образом, в настоящее время в STL такого контейнера нет. Вы можете продолжать использовать векторы с начальным размером .

1 голос
/ 27 марта 2019

К сожалению, в C ++ 20 не было добавлено ни одного нового контейнера (по крайней мере, ни одного, о котором я бы знал). Я согласен, однако, что такой контейнер будет очень полезным. Хотя простое использование std::vector<T> с reserve() и emplace_back() обычно будет работать нормально, оно часто генерирует худший код по сравнению с использованием простого new T[], поскольку использование emplace_back(), по-видимому, препятствует векторизации. Если вместо этого мы используем std::vector<T> с начальным размером, то у компиляторов, похоже, возникнут проблемы с оптимизацией инициализации значений элементов, даже если сразу будет перезаписан весь вектор. Поиграйте с примером здесь .

Вы можете использовать, например, оболочку типа

template <typename T>
struct default_init_wrapper
{
    T t;

public:
    default_init_wrapper() {}
    template <typename... Args>
    default_init_wrapper(Args&&... args) : t(std::forward<Args>(args)...) {}

    operator const T&() const { return t; }
    operator T&() { return t; }
};

и

std::vector<no_init_wrapper<T>> buffer(N);

чтобы избежать бесполезной инициализации для тривиальных типов. Это , похоже, приводит к коду , так же хорошо, как и к простой std::unique_ptr версии. Я бы не рекомендовал это, так как использовать его довольно уродливо и громоздко, так как вам придется работать с вектором обернутых элементов.

Полагаю, лучший вариант сейчас - просто свернуть свой собственный контейнер. Это может послужить отправной точкой (остерегайтесь ошибок):

template <typename T>
class dynamic_array
{
public:
    using value_type = T;
    using reference = T&;
    using const_reference = T&;
    using pointer = T*;
    using const_pointer = const T*;
    using iterator = T*;
    using const_iterator = const T*;
    using reverse_iterator = std::reverse_iterator<iterator>;
    using const_reverse_iterator = std::reverse_iterator<const_iterator>;
    using size_type = std::size_t;
    using difference_type = std::ptrdiff_t;

private:
    std::unique_ptr<T[]> elements;
    size_type num_elements = 0U;

    friend void swap(dynamic_array& a, dynamic_array& b)
    {
        using std::swap;
        swap(a.elements, b.elements);
        swap(a.num_elements, b.num_elements);
    }

    static auto alloc(size_type size)
    {
        return std::unique_ptr<T[]> { new T[size] };
    }

    void checkRange(size_type i) const
    {
        if (!(i < num_elements))
            throw std::out_of_range("dynamic_array index out of range");
    }

public:
    const_pointer data() const { return &elements[0]; }
    pointer data() { return &elements[0]; }

    const_iterator begin() const { return data(); }
    iterator begin() { return data(); }

    const_iterator end() const { return data() + num_elements; }
    iterator end() { return data() + num_elements; }

    const_reverse_iterator rbegin() const { return std::make_reverse_iterator(end()); }
    reverse_iterator rbegin() { return std::make_reverse_iterator(end()); }

    const_reverse_iterator rend() const { return std::make_reverse_iterator(begin()); }
    reverse_iterator rend() { return std::make_reverse_iterator(begin()); }

    const_reference operator [](size_type i) const { return elements[i]; }
    reference operator [](size_type i) { return elements[i]; }

    const_reference at(size_type i) const { return checkRange(i), elements[i]; }
    reference at(size_type i) { return checkRange(i), elements[i]; }

    size_type size() const { return num_elements; }

    constexpr size_type max_size() const { return std::numeric_limits<size_type>::max(); }

    bool empty() const { return std::size(*this) == 0U; }

    dynamic_array() = default;

    dynamic_array(size_type size)
        : elements(alloc(size)), num_elements(size)
    {
    }

    dynamic_array(std::initializer_list<T> elements)
        : elements(alloc(std::size(elements))), num_elements(std::size(elements))
    {
        std::copy(std::begin(elements), std::end(elements), std::begin(*this));
    }

    dynamic_array(const dynamic_array& arr)
    {
        auto new_elements = alloc(std::size(arr));
        std::copy(std::begin(arr), std::end(arr), &new_elements[0]);
        elements = std::move(new_elements);
        num_elements = std::size(arr);
    }

    dynamic_array(dynamic_array&&) = default;

    dynamic_array& operator =(const dynamic_array& arr)
    {
        return *this = dynamic_array(arr);
    }

    dynamic_array& operator =(dynamic_array&&) = default;

    void swap(dynamic_array& arr)
    {
        void swap(dynamic_array& a, dynamic_array& b);
        swap(*this, arr);
    }

    friend bool operator ==(const dynamic_array& a, const dynamic_array& b)
    {
        return std::equal(std::begin(a), std::end(a), std::begin(b));
    }

    friend bool operator !=(const dynamic_array& a, const dynamic_array& b)
    {
        return !(a == b);
    }
};
...