由于本人前段时间一直在进行图像处理的研究,大家都知道图像是二维的,故在程序中经常会有二维数组的使用,而在C++中是用二维指针T** value来表示二维数组。如果直接使用T** value就会在程序中经常出现二重for循环分配内存、二重for循环释放内存的代码——非常的无趣,非常的容易出错。(C++的指针使用方法请见另一篇博文http://www.cnblogs.com/xiangism/archive/2011/07/18/2109981.html )
因此本人写了一个TwoDimesionArray<T>的泛型类用以表示二维数组,写这个类的灵感来些于boost::shared_ptr。
boost::shared_ptr<T>是一个非常好用的智能指针。程序员只管new出指针对象,而不需要自己手动delete释放。shared_ptr最关键的地方在于,它实现了引用计数,当计数大于0时,对象一直存在;而当引用计数等于0时,则释放内存。为了提高性能,将一个shared_ptr<T>对象赋值给另一个shared_ptr<T>后,两个shared_ptr<T>对象将指向同一块内存,修改其中一个也将修改另一个对象的值。
1 void test1() 2 { 3 shared_ptr<int> va=shared_ptr<int>(new int(1)); 4 shared_ptr<int> vb=va; 5 (*vb)=2; 6 cout<<*va<<"\n"; //这里将输出2 7 }//当程序退出这个函数后,第3行末尾new出来的内存将自动被释放
本人的TwoDimesionArray<T>也想实现类似的功能。
先看具体用法:
TwoDimesionArray<int> creatValue() { TwoDimesionArray<int> values(1000,2000); //在Release模式下,这个类的效率可以和原生二维指针相媲美 values.SetAllValue(1); values.SetValue(10,20,100); return values; //可以将values作为函数返回值返回,这里只会引用计数加1,而不会有新对象产生 } void changeValue(TwoDimesionArray<int> values) //直接用作函数参数,指向的内存地址仍为原先的内存地址 { values.SetValue(10,20,200); //这里修改的值会影响到函数外 } void test1() { TwoDimesionArray<int> values=creatValue(); changeValue(values); cout<<values.GetValue(10,20)<<"\n" ; //这里将输出200 }
使用shared_ptr和TwoDimesionArray时就像在C#中使用对象(对象都是引用)一样,这也使得本人现在看C++代码比看C#代码更亲切了。
下面讲解其具体实现方式:
TwoDimesionArray<T>的类图
其中m_width,m_height分别表示二维数组的宽高;m_value是二维指针,指向元素真正所在的地址;m_count是引用计数,表示内存地址被引用的次数。
下面分别讲解类的成员函数——
一、构造函数
TwoDimesionArray() { m_width=shared_ptr<int>(new int(0)); m_height=shared_ptr<int>(new int(0)); m_count=shared_ptr<long>(new long(0)); //cout<<"construct\n"; } TwoDimesionArray(int _width,int _height) { m_width=shared_ptr<int>(new int(_width)); m_height=shared_ptr<int>(new int(_height)); m_value=new T *[_height]; //如果这里用new T*[_width]那么在取值时即可x,y正序 m_count=shared_ptr<long>(new long(1)); for (int j=0;j<_height;++j) { m_value[j]=new T[_width]; } //cout <<"w,h\n"; } TwoDimesionArray(const TwoDimesionArray &_rhs) :m_width(_rhs.m_width) ,m_height(_rhs.m_height) ,m_value(_rhs.m_value) ,m_count(_rhs.m_count) { if(*m_count!=0) ++(*m_count); }
此类接受无参数的构造函数和一个指定宽、高的构造函数。注意在第二个构造函数中,就指定了m_count=1,并且用for循环分配了内存。在复制构造函数中,将四个成员分别赋值后,再将引用计数自加1。在自加1之前之所有要判断是否为0,是为了防止下面的代码
void test3() { TwoDimesionArray<int> ints ; //这里引用计数为0 TwoDimesionArray<int> ints1(ints); //这里应该还为0 }
二、重载等号
TwoDimesionArray& operator=(const TwoDimesionArray &_r) { //cout<<"==\n"; if(this==&_r) return *this; dispose(); m_value=_r.m_value; m_width=_r.m_width; m_height=_r.m_height; m_count=_r.m_count;
if(*m_count!=0) ++(*m_count); return *this; }
这里先判断是否为自己赋值给自己。再进行成员变量的赋值,与复制构造函数类似。
三、get,set函数
const T& GetValue(const int x,const int y) const { if(!IsInArray(x,y)) { //boost::format fmt("%1%,%2%"); //fmt % x % y; ////string str=boost::lexical_cast<string>(x) //throw std::out_of_range(fmt.str()); //ASSERT(0); throw; } return m_value[y][x]; } T& GetValue(const int x,const int y) { if(!IsInArray(x,y)) { throw; } return m_value[y][x]; } const int GetWidth() const {return *m_width;} const int GetHeight() const {return *m_height;}
void SetAllValue(const T &_value) //N次在这个函数上栽跟头,开始这个函数名为SetValue(与下面的相同),当在循环中给每一项赋值时,本来应该是twoD.SetValue(i,j,value),而经常会写成twoD.SetValue(value) { for (int j=0;j<*m_height;++j) { for (int i=0;i<*m_width;++i) { m_value[j][i]=_value; } } } void SetValue(int x,int y,const T &_value) { if(!IsInArray(x,y)) { throw; } m_value[y][x]=_value; }
上面GetValue有两个重载形式,支持函数左值赋值。GetWidth,GetHeight分别为得到宽、高。
SetAllValue给二维数组每个元素赋同样的值,一般用在初始化中。SetValue给指定位置上的元素赋值。
注意引用到特定位置元素时,m_value[y][x]中x,y的顺序。
四、深度复制函数
TwoDimesionArray<T> DeepClone() const { TwoDimesionArray<T> r(*m_width,*m_height); for (int j=0;j<*m_height;++j) { for (int i=0;i<*m_width;++i) { r.m_value[j][i]=m_value[j][i]; } } return r; }
由于用等号操作符会使两个TwoDimesionArray<T>引用到同一个内存地址,故有必要提供一个深度复制函数。
五、析构函数
~TwoDimesionArray() { dispose(); //cout <<"Dispose\n"; } void dispose() { if(*m_count==0) return; --(*m_count); if(*m_count==0) { for (int j=0;j<*m_height;++j) { //这里必须加[],在有些情况下只能用delete[] delete[] m_value[j]; } // if(m_height!=0) // { delete[] m_value; /*} */ } }
类析构时,判断m_count是否为0,如果为0的话就用for循环进行delete释放内存。
总结:
本人使用这个类进行一年多图像处理,没有遇到功能上的问题,大大方便了自己对图像数据的处理。以后本人在研究游戏时也将继续用到这个类。
下面是这个类的源码(现在将这个类名改为L2DPtr,使用方法和上面一样)
#ifndef LITENICE_2DPTR_HPP #define LITENICE_2DPTR_HPP 1 #pragma once #include <boost/shared_ptr.hpp> #include <assert.h> namespace ln { // // L2dPtr.h // // (C) Copyright xiangism 2015 // Copyright (c) 2011-2015 // // 智能二维数组 // // See http://www.cnblogs.com/xiangism // template<typename T> class L2dPtr { public: L2dPtr() { m_width = boost::shared_ptr<int>(new int(0)); m_height = boost::shared_ptr<int>(new int(0)); m_count = boost::shared_ptr<int>(new int(0)); m_value = NULL; } L2dPtr(const int width, const int height) { m_width = boost::shared_ptr<int>(new int(width)); m_height = boost::shared_ptr<int>(new int(height)); m_count = boost::shared_ptr<int>(new int(1)); m_value = new T[width*height]; } L2dPtr(int width, int height, T *value) { m_width = boost::shared_ptr<int>(new int(width)); m_height = boost::shared_ptr<int>(new int(height)); m_count = boost::shared_ptr<int>(new int(2)); //TODO:注意这里是2,因为这里的数据是与外部共享的,所以这个类不应该销毁数据 m_value = value; } L2dPtr(const L2dPtr &rhs) : m_width(rhs.m_width), m_height(rhs.m_height), m_value(rhs.m_value), m_count(rhs.m_count) { if (*m_count != 0) { ++(*m_count); } } L2dPtr& operator = (const L2dPtr &r) { if (this == &r) return *this; dispose(); // 销毁原来的 m_value = r.m_value; m_width = r.m_width; m_height = r.m_height; m_count = r.m_count; ++(*m_count); return *this; } ~L2dPtr(void) { dispose(); } L2dPtr<T> DeepClone() { L2dPtr<T> clone(*m_width, *m_height); for (int j=0; j<*m_height; ++j) { for (int i=0; i<*m_width; ++i) { clone.SetValue(i, j, GetValue(i, j)); } } return clone; } const T& GetValue(const int x, const int y) const { if (!IsInArray(x, y)) { printf("GetValue_const Not In Array: %d, %d\n", x, y); assert(IsInArray(x, y)); } return m_value[GetIndex(x, y)]; } T& GetValue(const int x, const int y) { if (!IsInArray(x, y)) { printf("GetValue Not In Array: %d, %d\n", x, y); assert(IsInArray(x, y)); } return m_value[GetIndex(x, y)]; } int GetHeight() const { return *m_height; } int GetWidth() const { return *m_width; } void SetValue(const int x, const int y, const T &val) { if (!IsInArray(x, y)) { printf("Not In Array: %d, %d\n", x, y); assert(IsInArray(x, y)); } m_value[GetIndex(x, y)] = val; } void SetSize(const int width, const int height) { dispose(); m_width = boost::shared_ptr<int>(new int(width)); m_height = boost::shared_ptr<int>(new int(height)); m_count = boost::shared_ptr<int>(new int(1)); int size = width*height; if (size != 0) { m_value = new T[width*height]; } else { m_value = NULL; } } void SetAllValue(const T &value) { int sum = *m_width * *m_height; for (int i = 0; i<sum; ++i) { m_value[i] = value; } } bool IsInArray(int x, int y) const { if (x < 0 || x > *m_width-1 || y < 0 || y > *m_height-1) { return false; } return true; } bool IsEmpty() const { if (!m_width || !m_height) { return true; } if (*m_width == 0 || *m_height == 0) { return true; } return false; } T* GetBuffer() { return m_value; } private: void dispose() { if (*m_count == 0) { return ; } --(*m_count); if (*m_count == 0) { if (m_value) { delete [] m_value; m_value = NULL; } } // end if *m_count == 0 } // end function dispose() int GetIndex(const int x, const int y) const { return y*(*m_width) + x; } private: boost::shared_ptr<int> m_count; boost::shared_ptr<int> m_width; boost::shared_ptr<int> m_height; T *m_value; }; } // end namespcae ln #endif