一晃竟然已经过去了五六年的光景,再次想起回到这里留下点滴记录,往日的博文似乎还是昨日写就一般。只是稍稍翻阅逐条评阅,顿觉愧对来此的读者,当初无声无息的就悄然远遁,辜负各位来此交流之意,想想实在汗颜,在此给苦等回复不得的同仁道一声歉,怠慢各位了。
这两天想着自己编一编矩阵运算的程序,以便解决一些控制算法的开发需要。便在VS里面构建了一个类CMatrix,完了给这个类重载了诸多运算符,比如+、-、*之类的。而重载后的运算符大多要返回同样是CMatrix的对象,本来也是个天经地义的事情,却整出了幺蛾子,运行时出了问题。细一分析,才发现自己大意了,是内存上出现了问题。话休繁絮,先上了代码才是正事:
-
class CMatrix
-
{
-
public:
-
UINT r;
-
UINT c;
-
-
double* e;
-
CMatrix(UINT row, UINT col);
-
~CMatrix();
-
-
//重载运算符
-
CMatrix operator+(const CMatrix &B);
-
CMatrix operator-(const CMatrix &B);
-
CMatrix operator*(const CMatrix &B);
-
CMatrix operator*(const double &B);
-
CMatrix operator/(const double &B);
-
...
-
}
上面的r和c用来保存数组的行、列大小,e用来保存矩阵元素的值。很明显,因为矩阵的大小不固定,因此在构造函数里面要动态分配内存区给e来用。同样,在析构函数里面就要把这个分配的内存释放掉,才能保证程序不吃内存。构造函数和析构函数如下:
-
CMatrix::CMatrix(UINT row, UINT col)
-
{
-
if (row != 0 && col != 0)
-
{
-
r = row;
-
c = col;
-
-
e = new double[r*c];
-
memset(e, 0, r*c*sizeof(double));
-
}
-
}
-
-
CMatrix::~CMatrix()
-
{
-
r = 0;
-
c = 0;
-
if (e != NULL)
-
{
-
delete [] e;
-
e = NULL;
-
}
-
}
本来想着一个构造函数分配内存,一个析构函数释放内存,两个配合双剑合璧,天衣无缝了。不成想,一运行起来却被甩了好大一个耳刮子,竟然在重构的*操作符(此处是实现点乘的运算)爆出了非法内存访问的错误。代码也要贴上先:
-
CMatrix CMatrix::operator*(const double &B)
-
{
-
int i = 0, j = 0;
-
CMatrix ans(r, c);
-
-
for (i = 0; i < r; i++)
-
{
-
for (j = 0; j < c; j++)
-
{
-
ans(i, j) = e[i*c+j]*B;
-
}
-
}
-
-
return ans;
-
}
代码很简单,却爆出了内存访问的错误,加断点调试,最后却发现问题出在析构函数里面delete语句。一时间感觉还真的很懵,没捋清是个什么状况。delete出问题的话,应该就是访问了不该访问的地方。既然动态分配的内存不可能是不能访问的区域,那么就只有可能是这个内存区已经释放过了,出现了重复释放的问题。但是怎么会出现重复释放呢,每个对象只构造一次,也只析构一次,从哪里出现的重复释放呢?
不过仔细分析,却慢慢回过味来了,其实问题出现在重载操作符函数的返回上了。由于ans是在重载函数里面定义的局部变量,因此在函数返回的时候就会调用析构函数释放其成员变量e所指向的内存区,这是没有问题的。但是问题在于重载函数在返回的时候,会要把ans的内容返回给上级代码,这时候其实返回了另外一个CMatrix的对象,而且这个对象是完全拷贝了ans的。因为是完全拷贝,所以这个对象里面的成员变量e指向的也是ans对象所占用的内存区。那么当这个对象返回给上级代码之后,不管上级代码作何处理,这个对象都会在返回后释放,这时候它就会要调用析构函数去释放它的成员变量e所指的内存区。而很不幸,这个内存区已经被ans所释放了,这样就导致了重复释放的问题。
发现问题所在,那么接下来应该就是动手解决这个问题。不过这个时候却犯了难了,原因很简单,因为我无法分辨什么时候是释放我自己定义的ans对象,什么时候是释放返回值所拷贝的对象。对于类里面所定义的所有标志,在ans和返回对象里面都是一模一样的,简直就是真假美猴王。思来想去,似乎只有利用共享内存的信号量来实现对所分配的内存进行保护,才能保证这两个真假美猴王能够相安无事地对这片内存进行访问。不过,为了解决这个问题要兴师动众地请出信号量这尊大佛帮忙,感觉挺麻烦的,实在是感觉困顿不想动手。于是想想还是求助度娘吧,别说还真让我找到了这个相对简单的办法。那就是shared_ptr这个东东,没想到太久没有紧跟C语言的发展,都不知道C++11里面有个这么好用的玩意,真是帮了我的大忙。
度娘:shared_ptr是一种智能指针(smart pointer)。shared_ptr的作用有如同指针,但会记录有多少个shared_ptrs共同指向一个对象。这便是所谓的引用计数(reference counting)。一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。这在非环形数据结构中防止资源泄露很有帮助。
看这解释,真是为我所遇到的这个问题量身定做的解决方案啊,堪称完美解决了,废话不说,直接拿来用起:
-
#include <memory>
-
-
using namespace std;
-
-
typedef std::shared_ptr<double> MatMemSP;
-
-
class CMatrix
-
{
-
public:
-
UINT r;
-
UINT c;
-
MatMemSP sp_e;
-
double* e;
-
...
-
}
要用到shared_ptr,就必须包含头文件,并使用std命名空间。完了shared_ptr用模板<>来定义内存区间的指针类型,因为其本身是封装的一个类。我这里的内存区间是定义成double*的指针,所以使用shared_ptr的时候就采用来定义。在构造函数和析构函数里面就通过shared_ptr类型的成员变量sp_e来对分配的内存区进行管理:
-
CMatrix::CMatrix(UINT row, UINT col)
-
{
-
...
-
sp_e = MatMemSP(new double[r*c]);
-
e = sp_e.get();
-
...
-
}
-
-
CMatrix::~CMatrix()
-
{
-
...
-
sp_e.reset();
-
e = NULL;
-
...
-
}
在构造函数里面,将new double[r*c]分配的内存区赋给sp_e,让sp_e实现对这块内存区的管理。再让e指向刚刚分配的内存区,以便于其他成员函数用double指针进行使用。在析构函数里面通过调用sp_e.reset()对内存区进行释放,shared_ptr自己会对占用内存的对象进行计数,每次有对象调用reset()进行释放的时候就将计数减1,当减到0的时候才会真正释放内存区,这样就不会出现重复释放的情况了。
赶快编译,运行!走起,完美解决!
阅读(1601) | 评论(0) | 转发(0) |