连续空间的多维数组实现
Posted 小键233
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了连续空间的多维数组实现相关的知识,希望对你有一定的参考价值。
在我的这篇文章中,我提出了一种实现连续空间的二维数组的方法。
https://blog.csdn.net/u014613043/article/details/51543534
今天重新组织了一下新的实现方式,觉得有点意思,记录一下
设计思想
一个连续的多维空间数组,希望可以使用模板自动帮我们识别,这是个N 维的数组。
然后二维的数组就是这个实现的泛化。
于是,给出如下的类声明:
template<class T, size_t N>
class array_n
private:
size_t dimension[N];
unique_ptr<T[]> data;
;
解决参数个数问题及赋值问题
提供类的构造函数,希望可以按照N 的个数, 检查参数的个数对不对,还希望检查参数的内容是不是整数类型。
所幸,我们可以使用static_assert 进行编译期检查,可以使用 std::is_integral 检查是不是整数。
首先,我们希望编写一个对可变参数解包的函数,为 dimension 数组赋值
void assign(size_t* d)
template<class T, class ... Args>
void assign(size_t* d, T value, Args&& ... args)
static_assert(is_integral<T>::value, "array_n parameter type is not value");
d[0] = static_cast<size_t>(value);
assign(++d, args ...);
然后把以上内容串起来,可以编写一个类的构造函数:
template< class ... Args >
array_n(Args ... args)
using parameter_type = decltype(make_tuple(args ...));
constexpr int p_size = tuple_size< decltype(make_tuple(args ...)) >::value;
static_assert(p_size == N, "parametersize_not_equal_N");
assign(dimension, args ...);
init();
其中 p_size 会检查参数的个数是不是满足N 的数量,否则,会报错。
其中decltype 的语句只是编译期行为,并不会真正地运行,所以这里并不会有任何的额外的检查开销。
补充init 函数如下:
void init()
data.reset(new T[size()]);
size 的解包如下:
template<size_t N>
size_t mul(const size_t d[])
return d[N - 1] * mul<N - 1>(d);
template<>
size_t mul<0>(const size_t d[])
return 1;
//-------------
size_t size() const
return mul<N>(dimension);
index 索引方法
如何提供一个优雅的index 是我们考虑的重点 。这次我希望可以使用原始的 operator[] 操作符来进行index 的操作。
我希望可以如下提供index 的方法:
1)当N == 1 时,直接访问到特性的元素
2) 当N == 2 时,第一次operator[] 返回一个 T*,第二次operator[] 时原生指针就可以支持访问了
3) 当N >= 3 时,operator[] 返回一个包装器,这个包装器对每级的operator[] 进行索引计算,直到拿到最终的元素。
于是,我们首先需要针对N ,定义返回的类型:
template<size_t N>
struct next_type_helper
using value = array_n_index<T, N>;
;
template<>
struct next_type_helper<2>
using value = T * ;
;
template<>
struct next_type_helper<1>
using value = T & ;
;
using next_type = typename next_type_helper<N>::value;
其中array_n_index<T,N> 就是我们的包装器。
先不关心N > 2 的情况,我们继续把剩下的内容补充完全
next_type operator[](size_t index)
return index_operator<N>(index);
template<size_t N>
inline next_type index_operator(size_t index)
return next_type(*this, index);
template<>
inline next_type index_operator<2>(size_t index)
return data.get() + index * dimension[N - 1];
template<>
inline next_type index_operator<1>(size_t index)
return data[index];
针对N > 2 的情况,设计一个包装器。
template<class T, size_t N>
struct array_n_index
array_n_index(array_n<T, N>& in_array, size_t index)
...
private:
T* data; //pointer of data
size_t dimension[N-1]; //dimension
int now_dimension; // now index level
;
回顾一下访问index 的内容,假如存在 int value[row][col]
, 当需要访问index (i,j) 的内容时,表达式如下:
value + i * col + j
如果多了一维数组,那么访问如下: int value[row][col][third]
, index(i,j,k)
value + i*col*third + j * third + k
于是dimension 的设计如下:
dimension[0] = col * third
dimension[1] = third
dimension[2] = 1
考虑到从 array_n 的 operator[] 中已经调用了第一级,所以 dimension[0] 可以去掉了,变成:
dimension[0] = third
dimension[1] = 1
当维数变得更高的时候,这个规律依然适用。
补充一下剩下的实现,完整的包装器如下:
template<class T, size_t N>
struct array_n_index
array_n_index(array_n<T, N>& in_array, size_t index)
data = in_array.get_data();
dimension[N-2] = 1;//N-2
dimension[N - 3] = in_array.dimension[N - 1];
for (int i = N - 4; i >= 0; --i)
dimension[i] = in_array.dimension[i + 2] * dimension[i + 2];
now_dimension = 0;
//compute now
data += dimension[N-2] * in_array.dimension[1] * index;
array_n_index& operator[](size_t index)
data += index * dimension[now_dimension++];
return *this;
operator T&()
return *data;
array_n_index& operator= (const T& value)
*data = value;
return *this;
private:
T* data;
size_t dimension[N-1];
int now_dimension;
;
至此,核心的功能已经完成了。
使用
对类型进行别名:
template<class T>
using array2 = array_n<T, 2>;
template<class T>
using array3 = array_n<T, 3>;
template<class T>
using array4 = array_n<T, 4>;
就可以特化成特定维数的数组。
性能
对二维和三维的数组进行测试, 测试环境 Visual Studio 2017 , Windows10
template<class T>
void test2(T& value, int first, int second)
for (int i = 0; i < first; ++i)
for (int j = 0; j < second; ++j)
value[i][j] = 233;
template<class T>
void test3(T& value, int first, int second, int third)
for (int i = 0; i < first; ++i)
for (int j = 0; j < second; ++j)
for (int k = 0; k < third; ++k)
value[i][j][k] = 233;
int main()
const int first = 133;
const int second = 255;
const int third = 123;
int value[first][second];
auto start = steady_clock::now();
test2(value, first, second);
auto end = steady_clock::now();
auto dur = duration<double, std::milli>(end - start);
cout <<"native 2-array "<< dur.count() << endl;
array2<int> value(first, second);
auto start = steady_clock::now();
test2(value, first, second);
auto end = steady_clock::now();
auto dur = duration<double, std::milli>(end - start);
cout <<"array2 "<< dur.count() << endl;
vector< vector<int> > value(first, vector<int>( second) );
auto start = steady_clock::now();
test2(value, first, second);
auto end = steady_clock::now();
auto dur = duration<double, std::milli>(end - start);
cout <<"vector "<< dur.count() << endl;
array3<int> value(first,second,third);
auto start = steady_clock::now();
test3(value, first, second, third);
auto end = steady_clock::now();
auto dur = duration<double, std::milli>(end - start);
cout << "array3 " << dur.count() << endl;
vector< vector< vector<int> > > value(first, vector< vector<int> >(second, vector<int>(third)));
auto start = steady_clock::now();
test3(value, first, second, third);
auto end = steady_clock::now();
auto dur = duration<double, std::milli>(end - start);
cout << "native3 " << dur.count() << endl;
system("pause");
开了release 的输出如下:
native 2-array 0.0006
array2 0.0652
vector 0.0155
array3 2.4337
native3 3.0414
请按任意键继续. . .
可以看到,在二维方面 ,array2 的表现只能是差强人意
但是在三维方面的表现,比vector 嵌套三次的效果要好。
在非debug 下:
native 2-array 0.0945
array2 5.0919
vector 12.8314
array3 691.447
native3 2194.27
emmm, 这个结果就耐人寻味了,不知道vector<vector <int> >
的数据结果在release 下做了什么样的优化,可以让它如此地接近原生数组地性能。
完整code
#include<tuple>
#include <memory>
#include <type_traits>
using namespace std;
template<size_t N>
size_t mul(const size_t d[])
return d[N - 1] * mul<N - 1>(d);
template<>
size_t mul<0>(const size_t d[])
return 1;
void assign(size_t* d)
template<class T, class ... Args>
void assign(size_t* d, T value, Args&& ... args)
static_assert(is_integral<T>::value, "array_n parameter type is not value");
d[0] = static_cast<size_t>(value);
assign(++d, args ...);
template<class T, size_t N>
struct array_n_index;
template<class T, size_t N>
class array_n
template<size_t N>
struct next_type_helper
using value = array_n_index<T, N>;
;
template<>
struct next_type_helper<2>
using value = T * ;
;
template<>
struct next_type_helper<1>
using value = T & ;
;
using next_type = typename next_type_helper<N>::value;
friend struct array_n_index<T, N>;
public:
template< class ... Args >
array_n(Args ... args)
using parameter_type = decltype(make_tuple(args ...));
constexpr int p_size = tuple_size< decltype(make_tuple(args ...)) >::value;
static_assert(p_size == N, "parametersize_not_equal_N");
assign(dimension, args ...);
init();
size_t size() const
return mul<N>(dimension);
size_t get_dimension(int d) const
return dimension[d];
T* get_data()
return data.get();
next_type operator[](size_t index)
return index_operator<N>(index);
private:
template<size_t N>
inline next_type index_operator(size_t index)
return next_type(*this, index);
template<>
inline next_type index_operator<2>(size_t index)
return data.get() + index * dimension[N - 1];
template<>
inline next_type index_operator<1>(size_t index)
return data[index];
void init()
data.reset(new T[size()]);
size_t dimension[N];
unique_ptr<T[]> data;
;
template<class T, size_t N>
struct array_n_index
array_n_index(array_n<T, N>& in_array, size_t index)
data = in_array.get_data();
dimension[N-2] = 1;//N-2
dimension[N - 3] = in_array.dimension[N - 1];
for (int i = N - 4; i >= 0; --i)
dimension[i] = in_array.dimension[i + 2] * dimension[i + 2];
now_dimension = 0;
//compute now
data += dimension[N-2] * in_array.dimension[1] * index;
array_n_index& operator[](size_t index)
data += index * dimension[now_dimension++];
return *this;
operator T&()
return *data;
array_n_index& operator= (const T& value)
*data = value;
return *this;
private:
T* data;
size_t dimension[N-1];
int now_dimension;
;
template<class T>
using array2 = array_n<T, 2>;
template<class T>
using array3 = array_n<T, 3>;
template<class T>
using array4 = array_n<T, 4>;
以上是关于连续空间的多维数组实现的主要内容,如果未能解决你的问题,请参考以下文章