细说智能指针
提到指针,我们就会想到指针的高效,当然,滥用指针也会为我们带来许多的潜在bug。
提到指针,我们就会想到内存泄漏。比如,使用指针后忘记释放,久而久之,堆空间就会全部使用完,那么会带来很大的危害。再比如,两个指针指向同一片内存区域,我们对同一片区域进行了多次释放,同样会造成内存泄漏。
为了方便大家的理解,我们先来模拟一下,使用指针却忘记释放带来的危害。首先,我们要定义一个类。这次,还是定义女朋友类吧(之前写过一篇《细说C++的友元》用的就是女朋友类,这次还用这个吧,方便说明问题,更何况我们这群码畜怎么会有女朋友呢,没有女朋友只能自己创建了,哈哈哈哈)。
女生都喜欢自拍,尤其是漂亮的女生。所以,女生会有很多照片,对吧。那么,我们创建的这个女朋友类,就让她有照片吧。当然了,你女朋友的照片肯定不会随便给别人的吧,所以要把picutre这个变量声明为private类型。既然,女生喜欢自拍,并且发朋友圈,也就是说,其他人虽然得不到她的照片,却可以通过她的朋友圈看到她的自拍,也就意味着我们可以通过一个public函数访问picture这个变量。那么,我们现在来写代码。
class Girlfriend{ private: int pictures; public: Girlfriend ( int i ){ cout << "Girlfriend ( int i ) " << endl; /*这句代码是不需要的, 写在这里只是为了后期能够方便我们观察 */ this->pictures = i; } int getPic ( void ){ return this->pictures; } ~Girlfriend (){ cout << "~Girlfriend() " << endl; /*这句代码是不需要的, 写在这里只是方便我们观察 */ } };
接着,我们来写一个主函数,来使用这个女朋友类。
int main ( int argc, char** argv ){ Girlfriend* Alice = new Girlfriend( 100 ); cout << "my girlfriend's pictures are " << mp->getPic() << endl; system ( "pause" ); return 0; }
运行结果:
我们在主函数中做了什么事情呢?我们通过指针动态创建了一个对象,并且,我创建的这个女朋友Alice有100张照片。看到这里,很多人就会想,指针危险吗?好像没什么危险啊。不是使用正常吗,看到程序运行结果,程序不是完美的执行了嘛,好像也没什么。
首先,我们创建的这个指针Alice没有去释放,仅仅创建了一个,危害不大,但是多了之后呢。那比如,我们现在再来写一个主函数。
int main ( int argc, char** argv ){ Girlfriend* Alice = new Girlfriend( 100 ); Girlfriend* Lisa = new Girlfriend( 200 ); cout << "my girlfriend Alice's pictures are " << Alice->getPic() << endl; cout << "my girlfriend Lisa's pictures are " << Lisa->getPic() << endl; system ( "pause" ); return 0; }
运行结果:
我们先抛开指针不谈。我们就来谈谈女朋友。假如Alice是你女朋友,你手机里有她的100张照片,后来你们因为一些事情产生了矛盾分手了,于是你交了另外一个女朋友Lisa。有一次,Lisa翻你的手机,发现你竟然有100张前女友照片,她什么感受,估计要疯了,你说后果严不严重,所以,趁你的现女友没发现之前,赶紧把前女友照片给删了,赶紧把前女友照片释放掉,不然,后果……
现在,回归正题,我们使用指针创建对象,一个,两个,都没问题,那如果多了呢?100个,1000个,10000个,那内存还能受的了吗?所以,用完指针之后一定要释放指针。
但是,我们是人啊,不是机器,总会有遗忘的时候,那么我们有没有办法在指针使用完毕后,它自己释放呢?当有了这个需求后,我们就要开始思考解决办法了。很快,我们就有了解决方案:通过类对象模拟指针。指针有两个运算符,一个是->,另一个是星号,只要在类内重载这两个操作符就行了。那么,这个类的成员变量是什么呢?就是一个指针。通过类来模拟指针的,这就是智能指针了。现在,我们先来写一个简陋版的。
class SmartPointer{ private: Girlfriend* sp; public: SmartPointer ( Girlfriend* p = NULL ){ sp = p; } Girlfriend* operator -> (){ return sp; } Girlfriend& operator * (){ return *sp; } ~SmartPointer (){ delete sp; cout << "~SmartPointer() " << endl; /*这句代码完全没必要, 写在这里只是便于我们观察 */ } }
我们在这个智能指针类中,重载指针了指针的两个运算符。通过这样做,我们解决了使用完毕指针后自动释放的问题。那么,还有一个问题,如果两个指针指向同一片区域,这样在释放指针时也会造成内存泄漏,因为同一片区域被释放两次。这个问题怎么解决呢?通过重载拷贝构造函数和赋值操作符。我们现在类内实现拷贝构造函数。
SmartPointer ( const SmartPointer& obj ){ sp = obj.sp; const_cast<SmartPointer&>(obj).sp = NULL; }
我们实现了拷贝构造函数,因为同一片区域只能有一个指针指向,所以,我们在把obj指向的地址赋给sp后,要将obj的sp赋值为NULL。这里用到了一个强制类型转换。
现在,我们要在类内实现重载赋值操作符。
SmartPointer& operator = ( const SmartPointer& obj ){ if ( this != &obj ){ delete sp; sp = obj.sp; obj.sp = NULL; } return *this; }
到这里为止,我们就把智能指针类实现完毕了。同时,使用智能指针还可以避免指针比较或加减运算,因为这些运算会造成指针越界带来的bug。好了,我们看一下,运行结果,符不符合我们的预期。
哇,非常符合。指针使用完毕后完美释放。
下面是完整代码:
#include <iostream> #include <string> using namespace std; class Girlfriend{ private: int pictures; public: Girlfriend ( int i ){ this->pictures = i; cout << "Girlfriend ( int i )" << endl; } int getPic ( void ){ return this->pictures; } ~Girlfriend (){ cout << "~Girlfriend ()" << endl; } }; class SmartPointer{ private: Girlfriend* sp; public: SmartPointer ( Girlfriend* p = NULL ){ sp = p; cout << "SmartPointer ( Girlfriend* p = NULL )" << endl; } Girlfriend* operator -> (){ return sp; } Girlfriend& operator * (){ return *sp; } SmartPointer ( const SmartPointer& obj ){ sp = obj.sp; const_cast<SmartPointer&>(obj).sp = NULL; } SmartPointer& operator = ( const SmartPointer& obj ){ if ( this != &obj ){ delete sp; sp = obj.sp; const_cast<SmartPointer&>(obj).sp = NULL; } return *this; } bool isnull (){ return ( sp == NULL ); } ~SmartPointer (){ cout << "~SmartPointer()" << endl; delete sp; } }; int main ( int argc, char** argv ){ SmartPointer Alice = new Girlfriend( 100 ); SmartPointer Lisa = new Girlfriend( 200 ); cout << "my girlfriend Alice's pictures are " << Alice->getPic() << endl; cout << "my girlfriend Lisa's pictures are " << Lisa->getPic() << endl; system ( "pause" ); return 0; }
后记:
这篇文章写完也是花了我两个小时了,这其中的代码都是经过我自己电脑测试的。虽然写这篇文章只花了我两个小时,但是,我在写这篇文章之前就已经开始在构思了,该如何写,才能做到通俗易懂。看来真的,想写好一篇文章还是比较困难的,毕竟要把自己学会的东西,通过语言文字描述出来,还是不容易的。之前写的一篇《细说C++的友元》上了博客的推荐,这里非常感谢51cto的小编。因为,那篇文章的缘故,我尽心尽力的写了这篇文章。
希望看完的小伙伴能够学到些知识,如果觉得我哪里讲解的有误,也可以在评论中指出,或者哪里有不懂的地方,也可以在评论中留言,如果有空,我们可以探讨下。
同时,感谢51cto的小伙伴们,大佬们花时间耐着性子看完了这篇文章,谢谢,谢谢你们!
最后,当然是希望,看完的小伙伴们,有所收获后点个赞,表示支持。谢谢!
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------欢迎打赏!哈哈哈哈!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
ORA-00600 kcratr_nab_less_than_odr 处理小计
今天由于客户现场异常断电,oracle数据库又无法启动了。远程上去看看吧。 数据库只能mount,已经无法启动 SQL> select status from v$instance; STATUS ------------ MOUNTED SQL> ALTER DATABASE OPEN; ALTER DATABASE OPEN * ERROR at line 1: ORA-01589: must use RESETLOGS or NORESETLOGS option for database open 尝试recover和resetlogs open都不行 SQL> recover database; ORA-00283: recovery session canceled due to errors ORA-01610: recovery using the BACKUP CONTROLFILE option must be done SQL> ALTER DATABASE OPEN resetlogs; ALTER DATABASE OPEN resetl...
- 下一篇
解决XORM的时区问题
如果你升级使用了较为新版xorm(如v0.6.3)和go-sql-driver(如v1.3)的go类库,那么你就可能会遇到时区问题。 如 time.Parse("2006-01-02 15:04:05" ,"2018-01-15 12:11:12") // 2018-01-15T12:11:12+00:00 写入是数据库时候就会被改变为2018-01-15T20:11:12+00:00。 上述的就是时区问题,因为我们使用的是东8时区,默认会被设置为0时区,解决方案很简单,只需要在main函数中或者main包中初始化时区: time.LoadLocation("Asia/Shanghai") 数据库配置为 root:root@tcp(127.0.0.1:3306)/test?charset=utf8&interpolateParams=true xorm的初始化修改为: orm, err := initOrm(ds, maxIdleConn, maxOpenConn, debug) if err != nil { return nil, err } r.Value = orm o...
相关文章
文章评论
共有0条评论来说两句吧...