连续空间的多维数组实现

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>;

以上是关于连续空间的多维数组实现的主要内容,如果未能解决你的问题,请参考以下文章

std::vector 和多维数组的连续内存

多维数组

多维数组

连续数组多维表示的快速元素访问

C多级指针与多维数组

热榜!!!数据结构与算法:C语言版---数组与稀疏矩阵---强势来袭!