对于具有固定 运行-time-determined 大小的数组,是否有标准的 C++ class?

Is there a standard C++ class for arrays with fixed run-time-determined size?

我需要一个 运行 时间已知大小且无需调整大小的容器。 std::unique_ptr<T[]> 会很有用,但没有封装大小成员。同时 std::array 仅用于编译类型大小。因此,我需要这些 classes 与 no/minimal 开销的一些组合。

是否有满足我需求的标准 class,也许在即将推出的 C++20 中有什么?

对于 C++14,名为 std::dynarray 的东西是 proposed

std::dynarray is a sequence container that encapsulates arrays with a size that is fixed at construction and does not change throughout the lifetime of the object.

但是有 too many issues 但它没有成为标准的一部分。

所以目前STL中不存在这样的容器。您可以继续使用带有 initial size.

的向量

使用std::vector。这是 STL 中运行时大小数组的 class。

它允许您调整它的大小或将元素推入其中:

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

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

评论中提到的一些问题:

vector has an excessive interface

std::array也是如此。 std::array 中有 20 多个函数,包括运算符。

不要使用不需要的东西。您无需为不会使用的功能付费。它甚至不会增加您的二进制大小。

vector will force initialize items on resize. As far as I know, it is not allowed to use operator[] for indexes >= size (despite calling reserve).

这不是它的用途。保留时,您应该使用 resize 或将元素推入其中来调整向量的大小。你说 vector 会强制将元素初始化到其中,但问题是你不能在未构造的对象上调用 operator=,包括 ints。

这是一个使用保留的例子:

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

vec.reserve(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

请记住,这种分配和用例的可能性很高,编译器可能会完全忽略向量中元素的构造。您的代码中可能没有开销。

如果您的代码符合非常精确的性能规范,我会建议 measureprofile。如果您没有这样的规范,这很可能是过早的优化。内存分配的成本 完全 超出了逐一初始化元素所花费的时间。

您的程序的其他部分可能会被重构以获得比简单初始化所能提供的性能更高的性能。事实上,妨碍它可能会阻碍优化并使您的程序变慢。

不幸的是,C++ 20 中没有添加新的容器(至少 none 我知道)。但是,我同意这样的容器非常有用。虽然仅将 std::vector<T>reserve()emplace_back() 一起使用通常可以,但与使用普通 new T[] 作为 emplace_back() 相比,它确实经常生成劣质代码似乎抑制矢量化。如果我们改为使用具有初始大小的 std::vector<T> ,编译器似乎无法优化元素的值初始化,即使整个向量随后将被覆盖。 Play with an example here.

例如,您可以使用像

这样的包装器
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);

避免对琐碎类型进行无用的初始化。这样做 seems to lead to code 与普通 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);
    }
};

按照您的建议使用 std::unique_ptr<T[]> 分配内存,但要使用它 - 构造一个 std::span (在 C++20 中;在 C++20 之前的 gsl::span)原始指针和元素的数量,并传递跨度(按值;跨度是引用类型,有点)。跨度将为您提供容器的所有花里胡哨的东西:大小、迭代器、范围、工作。

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

int main() {

    // ... etc. ...

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

    // ... etc. ...
}

有关跨度的详细信息,请参阅:

使用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
};