以下是将unique_ptr<T[]> 封装在模拟RandomAccessRange 的一流值类型中的绝对最小值:
#include <memory>
template <typename T>
struct dyn_array {
explicit dyn_array(size_t n)
: _n(n), _data(std::make_unique<T[]>(n)) { }
auto begin() const { return _data.get(); }
auto end() const { return begin() + _n; }
auto begin() { return _data.get(); }
auto end() { return begin() + _n; }
auto size() const { return _n; }
private:
size_t _n {};
std::unique_ptr<T[]> _data;
};
那是 15 行代码。看到它Live
int main() {
using std::begin;
using std::end;
for (int n; (n = size_gen(prng)) != 10;) {
dyn_array<double> data(n);
std::iota(begin(data), end(data), 0);
static_assert(sizeof(data) == 2*sizeof(void*));
fmt::print("Size {} data {}\n", n, data);
}
}
打印例如
Size 8 data {0, 1, 2, 3, 4, 5, 6, 7}
Size 6 data {0, 1, 2, 3, 4, 5}
Size 7 data {0, 1, 2, 3, 4, 5, 6}
Size 6 data {0, 1, 2, 3, 4, 5}
我评论了两个版本
奖励:单指针大小
在上面的基础上,我意识到大小可以在分配使dyn_array的大小正好是1个指针。
Live Demo
#include <memory>
#include <cstdlib> // committing the sin of malloc for optimization
template <typename T>
struct dyn_array {
dyn_array(std::initializer_list<T> init)
: _imp(allocate(init.size(), true))
{ std::uninitialized_move(init.begin(), init.end(), begin()); }
dyn_array(dyn_array const& rhs)
: _imp(allocate(rhs.size(), true))
{ std::uninitialized_copy(rhs.begin(), rhs.end(), begin()); }
dyn_array(dyn_array&& rhs) { rhs.swap(*this); }
dyn_array& operator=(dyn_array rhs) { rhs.swap(*this); }
explicit dyn_array(size_t n = 0)
: _imp(allocate(n))
{ }
auto size() const { return _imp? _imp->_n : 0ull; }
auto begin() const { return _imp? _imp->_data + 0 : nullptr; }
auto begin() { return _imp? _imp->_data + 0 : nullptr; }
auto end() const { return begin() + size(); }
auto end() { return begin() + size(); }
auto empty() const { return size() == 0; }
bool operator==(dyn_array const& rhs) const {
return size() == rhs.size() &&
std::equal(rhs.begin(), rhs.end(), begin());
};
void swap(dyn_array& rhs) {
std::swap(_imp, rhs._imp);
}
private:
struct Impl {
size_t _n;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
T _data[]; // C99 extension
#pragma GCC diagnostic pop
};
struct Deleter {
void operator()(Impl* s) const {
while (s->_n) { s->_data[--(s->_n)].~T(); }
std::free(s);
}
};
using Ptr = std::unique_ptr<Impl, Deleter>;
Ptr _imp;
static Ptr allocate(size_t n, bool uninitialized = false) {
if (!n)
return {};
auto p = std::malloc(sizeof(Impl) + n*sizeof(T)); // could be moreconservative
auto s = Ptr(reinterpret_cast<Impl*>(p));
s->_n = n;
if (!uninitialized)
std::uninitialized_default_construct_n(s->_data, n);
return s;
}
};
我们可以用作:
#include <fmt/ranges.h>
static size_t constructions = 0;
static size_t default_ctor, copy_ctor = 0;
static size_t destructions = 0;
struct Sensor final {
Sensor() { ++constructions; ++default_ctor; }
Sensor(Sensor const&) { ++constructions; ++copy_ctor; }
~Sensor() { ++destructions; }
};
int main() {
fmt::print("With initializers: {}, {}\n",
dyn_array{3.1415f},
dyn_array{"one", "two", "three"});
fmt::print("Without: {}, {}\n",
dyn_array<std::string_view>{3},
dyn_array<float>{1},
dyn_array<int>{}); // empty by default, no allocation
auto a = dyn_array{3,2,1};
fmt::print("sizeof(a) == sizeof(void*)? {}\n", sizeof(a) == sizeof(void*));
auto copy = a;
fmt::print("copy: {} == {}? {}\n", copy, a, (copy == a));
auto move = std::move(copy);
fmt::print("move: {} == {}? {}\n", move, a, (move == a));
fmt::print("copy now moved-from: {}, empty? {}\n", copy, copy.empty());
dyn_array<Sensor>(4); // test destructors
fmt::print("constructions({}) and destructions({}) match? {}\n",
constructions, destructions, constructions == destructions);
fmt::print("all default, no copy ctors? {}\n",
(copy_ctor == 0) && (default_ctor == constructions));
dyn_array { Sensor{}, Sensor{}, Sensor{} };
fmt::print("constructions({}) and destructions({}) match? {}\n",
constructions, destructions, constructions == destructions);
fmt::print("initializers({}) were uninitialized-copied: {}\n",
copy_ctor,
(copy_ctor == 3) && (default_ctor + copy_ctor == constructions));
}
印刷:
With initializers: {3.1415}, {"one", "two", "three"}
Without: {"", "", ""}, {1}
sizeof(a) == sizeof(void*)? true
copy: {3, 2, 1} == {3, 2, 1}? true
move: {3, 2, 1} == {3, 2, 1}? true
copy now moved-from: {}, empty? true
constructions(4) and destructions(4) match? true
all default, no copy ctors? true
constructions(10) and destructions(10) match? true
initializers(3) were uninitialized-copied: true
当然你可以不使用C99 flexible array members 来做到这一点,但我现在不想手动干预对齐。
奖励:[] 索引、front、back、at 访问器
这些添加起来非常简单:
auto& operator[](size_t n) const { return *(begin() + n); }
auto& operator[](size_t n) { return *(begin() + n); }
auto& at(size_t n) const { return n<size()?*(begin() + n): throw std::out_of_range("dyn_array::at"); }
auto& at(size_t n) { return n<size()?*(begin() + n): throw std::out_of_range("dyn_array::at"); }
auto& front() const { return at(0); }
auto& front() { return at(0); }
auto& back() const { return at(size()-1); }
auto& back() { return at(size()-1); }
当然 push_back/erase/etc 已经退出,因为构建后大小不会改变。