如何解决访问自定义2维C ++容器元素的最佳方法
我有一个非常基本的2D C ++容器,该容器依赖于一维容器(std :: array或std :: vector)和“高级”索引。预计该容器不能存储很多元素,甚至不能存储500个元素。什么是访问其元素的最佳方法?
起初,我使用int
在迭代容器时直接为元素建立索引。但是,经过阅读后,诸如this one之类的帖子使我转而使用std::size_t
。 (切换主要是为了养成良好的习惯,而不是出于容器的要求。但是other sources让我怀疑这是否真的是一种好习惯。)
结果,我不得不重新调整几个for()
循环,以避免下溢实现的错误,例如当检查相邻元素的值时,或者当元素在行或列中向后迭代时。这些更改继而降低了可读性,并使索引更易于出错。 (我的经验绝对是这件事的一个因素。)
我该怎么办?
- 坚持
std::size_t
- 继续使用
int
(同时还限制了2D存储的大小,以免发生不太可能的溢出) - 通过为列和行创建循环来保持基于范围或迭代器的循环(欢迎使用建议和链接)
- 其他东西
提前谢谢!
P.S .:甚至C ++ 20功能都受到欢迎!
解决方法
std::ptrdiff_t
是签名的std::size_t
。
我用那个。我听说过该标准应该使用的论点,但要付出最大的一点代价。
编写一个大步友好的迭代器有点麻烦。这是一个草图:
template<class T,class Stride=std::integral_constant<std::size_t,1>>
struct stride_iterator {
using difference_type = std::ptrdiff_t;
using value_type = T;
using reference= T&;
private:
T* dataptr = nullptr;
Stride datastride = {};
T* data() const { return dataptr; }
T*& data() { return dataptr; }
Stride stride() const { return datastride; }
public:
explicit stride_iterator( T* ptr,Stride s ):
dataptr(ptr),datastride{ std::move(s) }
{}
explicit stride_iterator( T* ptr ):
dataptr(ptr)
{}
stride_iterator():stride_iterator(nullptr) {}
stride_iterator(stride_iterator const&)=default;
stride_iterator& operator=(stride_iterator const&)& =default;
stride_iterator(stride_iterator &&)=default;
stride_iterator& operator=(stride_iterator &&)& =default;
T& operator*() const { return *data(); }
T* operator->() const { return data(); }
T& operator[](std::ptrdiff_t n) const {
return *(*this+n);
}
stride_iterator& operator+=( std::ptrdiff_t n )& {
data() += (n*stride());
return *this;
}
stride_iterator& operator-=( std::ptrdiff_t n )& {
data() -= (n*stride());
return *this;
}
friend stride_iterator operator+( std::ptrdiff_t rhs,stride_iterator lhs ) {
return std::move(lhs)+rhs;
}
friend stride_iterator operator+( stride_iterator lhs,std::ptrdiff_t rhs ) {
lhs += rhs;
return lhs;
}
friend stride_iterator operator-( stride_iterator lhs,std::ptrdiff_t rhs ) {
lhs += rhs;
return lhs;
}
stride_iterator& operator++() {
*this += 1;
return *this;
}
stride_iterator& operator--() {
*this -= 1;
return *this;
}
stride_iterator operator++(int) {
auto r = *this;
++*this;
return r;
}
stride_iterator operator--(int) {
auto r = *this;
--*this;
return r;
}
friend std::ptrdiff_t operator-( stride_iterator const& lhs,stride_iterator const& rhs ) {
return (lhs.data()-rhs.data())/stride();
}
friend bool operator<( stride_iterator const& lhs,stride_iterator const& rhs ) {
return lhs.data() < rhs.data();
}
friend bool operator<=( stride_iterator const& lhs,stride_iterator const& rhs ) {
return lhs.data() <= rhs.data();
}
friend bool operator>( stride_iterator const& lhs,stride_iterator const& rhs ) {
return lhs.data() > rhs.data();
}
friend bool operator>=( stride_iterator const& lhs,stride_iterator const& rhs ) {
return lhs.data() >= rhs.data();
}
friend bool operator==( stride_iterator const& lhs,stride_iterator const& rhs ) {
return lhs.data() == rhs.data();
}
friend bool operator!=( stride_iterator const& lhs,stride_iterator const& rhs ) {
return lhs.data() != rhs.data();
}
};
template<class It>
struct range {
It b,e;
It begin() const { return b; }
It end() const { return e; }
decltype(auto) operator[]( std::ptrdiff_t i ) const {
return b[i];
}
std::size_t size() const { return end()-begin(); }
bool empty() const { return end()==begin(); }
};
struct toy_matrix {
std::ptrdiff_t width = 1;
std::ptrdiff_t height = 1;
std::vector<int> data = std::vector<int>( 1 );
toy_matrix() = default;
toy_matrix( std::size_t w,std::size_t h ):width(w),height(h),data(w*h) {}
toy_matrix( std::size_t w,std::size_t h,std::initializer_list<int> il ):width(w),data(il) {
data.resize(w*h);
}
range<stride_iterator<int>> row( std::ptrdiff_t i ) {
int* ptr = data.data();
ptr += height * i;
return { stride_iterator<int>{ ptr },stride_iterator<int>{ ptr + width } };
}
range<stride_iterator<int,std::ptrdiff_t>> column( std::ptrdiff_t i ) {
int* ptr = data.data();
ptr += i;
return { stride_iterator<int,std::ptrdiff_t>{ ptr,width },stride_iterator<int,std::ptrdiff_t>{ ptr + height * width,width } };
}
range<stride_iterator<int>> operator[]( std::ptrdiff_t i ) {
return row(i);
}
int& operator()( std::ptrdiff_t x,std::ptrdiff_t y ) {
return (*this)[x][y];
}
};
测试代码:
toy_matrix m{ 5,4,{
1,2,3,5,10,20,30,40,50,100,200,300,400,500,1000,2000,3000,4000,5000,}};
for (auto x : m.column(0)) {
std::cout << x << "\n";
}
for (auto x : m.column(1)) {
std::cout << x << "\n";
}
for (auto x : m.column(2)) {
std::cout << x << "\n";
}
输出:
,1 10 100 1000 2 20 200 2000 3 30 300 3000
Stroustrup首选带符号的整数类型,除非有充分的理由使用无符号的整数类型。
STL的创建者Alexander Stepanov首选size_t
(未定义)作为存储容器大小的类型。
因此区别非常微妙。
我的个人观点:
- 如果您是组织的一部分,请使用其编码标准
- 始终避免使用老式的循环,而希望使用基于范围的循环(
for (auto x: vec)
等)。有了这个,就没有索引,没有bug的余地。 - 如果您需要在循环中使用索引,请首选
int
并使用C ++ 20的std::ssize
(容器的带符号大小)。
理论上:
-
std::ptrdiff_t
太长了,无法编写:-),而且您几乎永远不会拥有超过20亿个元素的容器。
表达式中的 -
int
更自然,更不易出错 - 在循环(或任何表达式)中仅使用一个无符号变量通常会迫使一个将许多其他变量转换为无符号类型,只是为了使编译器警告静音。代码变得丑陋,非常丑陋。
- 带符号整数更易于优化(如问题所在链接中所述)
也就是说,多年来我一直使用size_t
(组织)或粗心的(int)v.size()
(私有代码)。 std::ssize
应该早已添加到图书馆中。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。