我需要一个运行时大小已知的容器,而无需调整大小。 std::unique_ptr<T[]>
很有用,但是没有封装的size成员。同时std::array
仅适用于编译类型大小。因此,我需要这些类的某种组合,而没有/最小的开销。
是否有满足我需求的标准类,也许在即将到来的C ++ 20中有用吗?
答案 0 :(得分:11)
使用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
评论中提到的一些问题:
vector接口过多
std::array
也是如此。 std::array
中有20多个函数,包括运算符。
不要使用您不需要的东西。您无需为不使用的功能付费。它甚至都不会增加您的二进制文件大小。
vector将强制初始化项目的大小。据我所知,不允许对索引> = size使用
operator[]
(尽管调用reserve
)。
这不是应使用的方式。保留时,应使用resize
或将元素推入向量来调整向量的大小。您说vector将强制将初始化元素放入其中,但是问题是您无法在未构造的对象(包括int)上调用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
请记住,此类分配和用例的可能性很高,编译器可能会完全忽略向量中元素的构造。您的代码可能没有任何开销。
如果您的代码受非常精确的性能规范的约束,我建议进行 measure 和 profile 。如果您没有这样的规范,则很可能是过早的优化。内存分配的成本完全可以衡量一次一个元素初始化的时间。
您可以重构程序的其他部分,以获取比普通初始化所能提供的性能更多的性能。实际上,采用这种方式可能会阻碍优化并降低程序速度。
答案 1 :(得分:5)
按照您的建议使用open /Library
分配内存,但要使用它-从原始数据构造一个export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_202.jdk/Contents/Home
(在C ++ 20中;在C ++ 20之前为gsl::span
)指针和元素数,然后传递范围(按值;范围是引用类型,有点儿)。跨度将为您提供容器的所有细节:大小,迭代器,适用范围,作品。
std::unique_ptr<T[]>
有关跨度的更多信息,请参见:
答案 2 :(得分:4)
使用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 :(得分:3)
对于C ++ 14,称为std::dynarray
的事物是proposed:
std :: dynarray是一个序列容器,该容器封装大小固定在构造上的数组,并且在对象的整个生命周期中都不会改变。
但是有too many issues,它没有成为标准的一部分。
因此,STL中当前没有这样的容器。您可以继续使用带有initial size的向量。
答案 4 :(得分:1)
不幸的是,在C ++ 20中没有添加任何新容器(至少我没有意识到)。但是,我同意这样的容器将非常有用。尽管仅将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);
}
};