c++智能指针的原理与简单实现
2017-03-06 22:53
489 查看
一、问题的引入
先看一段简单的代码如下:
在这段代码里面,定义了一个简单的类Person,它只有一个name成员和简单的构造函数和析构函数。在test函数里面,使用Person *p = new Person()构造了一个person对象,并在main函数里面调用了test()。很明显,在test()里面并没有去delete p ,将不会调用析构函数,因此可能在main函数退出前造成内存泄漏。
我们当然可以在test()函数里面将“Person *p = new Person() ” 替换成 “Person p ; ” ,对应为p分配的内存是在栈里面,因此在test()函数退出前会自动释放分配的内存(执行析构函数),但是这样就无法使用指针操作了(除非我们每次手工执行delete操作)。
二 、 智能指针的引入
定义一个Sp类,如下:
在test()函数里面,我们同样使用new来构造出来一个Person的实例化对象,与前面不同的是,这个对象指针作为一个参数传给Sp类的构造函数构造出来了一个实例化对象s。当test()函数执行完后,对象s的析构函数被调用,在析构函数里面再去delete Person的实例化对象。这是一种非常巧妙的方法,使用Sp s 代替了 Person * , 由Sp为我们做好了delete操作,这样即使不手工执行delet
4000
e操作也不会造成内存泄漏的问题 。
因此,此时的Sp s 就可以替代 Person * 指针,这就是智能指针的智能之处,最终目的还是为了防止我们因为忘记delete操作而造成内存泄漏。
接下来我们在Person类里面添加一个普通的成员函数printInfo
在test()函数里面,我们要调用p的printInfo,即 s->printInfo( ) ,但是Sp 类里面并没有定义这个函数,我们可以在Sp类里面重载“->”:
同样,我们也应该重载“*”,它返回对象的引用:
三 、 智能指针的完善
1、为智能指针(Sp)添加拷贝构造函数
代码如下:
在这个Sp类里面,我们实现了两个构造函数:使用Person * 来构造、使用拷贝构造函数构造。
这里要注意:拷贝构造函数中要将参数Sp & 这个引用声明为const,否则编译器会报错!!!(编译器不知道先使用Person * 来构造还是直接使用拷贝构造函数构造 s)。
2、添加引用计数
先看看如下代码:
编译、运行该程序 ,发现程序崩溃。
分析原因:
(1)由前一点分析,在main函数里,它首先构造出来一个Person 的实例化对象(假设为p),并使s的p成员指向new出来的p;
(2)接着执行Sp s2 = s,将调用Sp的拷贝构造函数,在里面的s2 的p成员同样指向传入的s的p成员;
(3)main函数退出之前,将分别调用s、s2的析构函数,它们均delete p对应的内存;
因此,问题在于p对象被销毁了两次,这是明显不合理的。第一次释放了p对应的内存后,p = NULL;第二次来释放时,对象已经不存在了,main函数找不到p对象,会出现异常,程序自然崩溃了。
这里,通常的解决方法是给对象p添加一个引用计数,两次构造后p被引用了两次,引用计数为2;当第一次调用Sp的析构函数时,由于对象p仍然被引用,所以先不销毁它,仅仅减少它的引用计数为1;第二次调用Sp的析构函数时,先减少p的引用计数为0,即不被任何对象所引用了,再来销毁p。
(1)为Person添加引用计数,并增加相应接口,在第一次构造Person时初始化计数值为0:
(2)在Sp的构造函数里面,调用Person的incStrong增加引用;在析构函数里面,先调用decStrong减少引用,若此时引用值为0,则销毁它:
3、使用类模板
我们可以将引用计数独立出来,实现一个名为RefBase的基类,并将Sp类定义为模板类:
总结:
我们可以使用智能指针代替平时的指针操作 ,使用智能指针来自动维护对象的生命周期。实际上,智能指针的目的就是防止在new操作后忘记delete操作从而造成内存泄漏,它仅仅是一种手段。Android源代码庞大且复杂,大量地采用智能指针来维护对象的生命周期,而在实际编程中,我们要时刻关注对象的生命周期,不应该依赖于这些手段而忽视对内存的重视,因为c/c++编程的本质就是在操作内存。
先看一段简单的代码如下:
#include<iostream> using namespace std; class Person { private: char *name ; public: Person() { cout<<"Person()"<<endl ; } ~Person() { cout<<"~Person()"<<endl ; } } ; void test(void) { Person *p = new Person() ; } int main(int argc , char** argv) { test(); return 0 ; }
在这段代码里面,定义了一个简单的类Person,它只有一个name成员和简单的构造函数和析构函数。在test函数里面,使用Person *p = new Person()构造了一个person对象,并在main函数里面调用了test()。很明显,在test()里面并没有去delete p ,将不会调用析构函数,因此可能在main函数退出前造成内存泄漏。
我们当然可以在test()函数里面将“Person *p = new Person() ” 替换成 “Person p ; ” ,对应为p分配的内存是在栈里面,因此在test()函数退出前会自动释放分配的内存(执行析构函数),但是这样就无法使用指针操作了(除非我们每次手工执行delete操作)。
二 、 智能指针的引入
定义一个Sp类,如下:
#include<iostream> using namespace std; class Person { private: char *name ; public: Person() { cout<<"Person()"<<endl ; } ~Person() { cout<<"~Person()"<<endl ; } } ; class Sp { private: Person *p ; public: Sp(Person *per){ cout<<"Sp(Person *per)"<<endl ; p = per ; } ~Sp(){ cout<<"~Sp()"<<endl ; if(p){ delete p ; p = NULL ; } } } ; void test(void) { //Person *p = new Person() ; Sp s = new Person() ; } int main(int argc , char** argv) { test(); return 0 ; }
在test()函数里面,我们同样使用new来构造出来一个Person的实例化对象,与前面不同的是,这个对象指针作为一个参数传给Sp类的构造函数构造出来了一个实例化对象s。当test()函数执行完后,对象s的析构函数被调用,在析构函数里面再去delete Person的实例化对象。这是一种非常巧妙的方法,使用Sp s 代替了 Person * , 由Sp为我们做好了delete操作,这样即使不手工执行delet
4000
e操作也不会造成内存泄漏的问题 。
因此,此时的Sp s 就可以替代 Person * 指针,这就是智能指针的智能之处,最终目的还是为了防止我们因为忘记delete操作而造成内存泄漏。
接下来我们在Person类里面添加一个普通的成员函数printInfo
void printInfo(void){ cout<<"just for test !"<<endl ; }
在test()函数里面,我们要调用p的printInfo,即 s->printInfo( ) ,但是Sp 类里面并没有定义这个函数,我们可以在Sp类里面重载“->”:
Person * operator->(){ return p ; }
同样,我们也应该重载“*”,它返回对象的引用:
Person& operator*(){ return *p ; }
这样,就可以使用s->printInfo( ) 或者 (*s).printInfo() ;
三 、 智能指针的完善
1、为智能指针(Sp)添加拷贝构造函数
代码如下:
class Sp {
private:
Person *p ;
public:
/*使用Person *来构造*/
Sp(Person *per){
cout<<"Sp(Person *per)"<<endl ;
p = per ;
}
/*拷贝构造函数*/
Sp(const Sp &other){
cout<<"Sp(Sp &other)"<<endl ;
p = other.p;
}
~Sp(){
cout<<"~Sp()"<<endl ;
if(p){
delete p ;
p = NULL ;
}
}
Person * operator->(){ return p ; }} ;
在这个Sp类里面,我们实现了两个构造函数:使用Person * 来构造、使用拷贝构造函数构造。
这里要注意:拷贝构造函数中要将参数Sp & 这个引用声明为const,否则编译器会报错!!!(编译器不知道先使用Person * 来构造还是直接使用拷贝构造函数构造 s)。
2、添加引用计数
先看看如下代码:
#include<iostream>
using namespace std;
class Person {
private:
char *name ;
public:
Person() {
cout<<"Person()"<<endl ;
}
~Person() {
cout<<"~Person()"<<endl ;
}
void printInfo(void){ cout<<"just for test !"<<endl ; }
} ;
class Sp {
private:
Person *p ;
public:
/*使用Person *来构造*/
Sp(Person *per){
cout<<"Sp(Person *per)"<<endl ;
p = per ;
}
/*拷贝构造函数*/
Sp(const Sp &other){
cout<<"Sp(Sp &other)"<<endl ;
p = other.p;
}
~Sp(){
cout<<"~Sp()"<<endl ;
if(p){
delete p ;
p = NULL ;
}
}
} ;
int main(int argc , char** argv)
{
Sp s = new Person() ;
Sp s2 = s; return 0 ;
}
编译、运行该程序 ,发现程序崩溃。
分析原因:
(1)由前一点分析,在main函数里,它首先构造出来一个Person 的实例化对象(假设为p),并使s的p成员指向new出来的p;
(2)接着执行Sp s2 = s,将调用Sp的拷贝构造函数,在里面的s2 的p成员同样指向传入的s的p成员;
(3)main函数退出之前,将分别调用s、s2的析构函数,它们均delete p对应的内存;
因此,问题在于p对象被销毁了两次,这是明显不合理的。第一次释放了p对应的内存后,p = NULL;第二次来释放时,对象已经不存在了,main函数找不到p对象,会出现异常,程序自然崩溃了。
这里,通常的解决方法是给对象p添加一个引用计数,两次构造后p被引用了两次,引用计数为2;当第一次调用Sp的析构函数时,由于对象p仍然被引用,所以先不销毁它,仅仅减少它的引用计数为1;第二次调用Sp的析构函数时,先减少p的引用计数为0,即不被任何对象所引用了,再来销毁p。
(1)为Person添加引用计数,并增加相应接口,在第一次构造Person时初始化计数值为0:
class Person {
private:
char *name ;
/*添加引用计数*/
int count;
public:
void incStrong() {count ++ ;}
void decStrong() {count -- ;}
int getStrong() {return count ;}
Person(): count(0) {
cout<<"Person()"<<endl ;
}
~Person() {
cout<<"~Person()"<<endl ;
}
void printInfo(void){ cout<<"just for test !"<<endl ; }
} ;
(2)在Sp的构造函数里面,调用Person的incStrong增加引用;在析构函数里面,先调用decStrong减少引用,若此时引用值为0,则销毁它:
class Sp { private: Person *p ; public: /*使用Person *来构造*/ Sp(Person *per){ cout<<"Sp(Person *per)"<<endl ; p = per ; p->incStrong(); } /*拷贝构造函数*/ Sp(const Sp &other){ cout<<"Sp(Sp &other)"<<endl ; p = other.p; p->incStrong() ; } ~Sp(){ cout<<"~Sp()"<<endl ; if(p){ p->decStrong() ; if(p->getStrong() == 0) { delete p ; p = NULL ; } } } } ;
3、使用类模板
我们可以将引用计数独立出来,实现一个名为RefBase的基类,并将Sp类定义为模板类:
#include<iostream>
using namespace std;
class RefBase {
private:
int count;
public:
RefBase() : count(0) {} ;
void incStrong() {count ++ ;}
void decStrong() {count -- ;}
int getStrong() {return count ;}
};
class Person :public RefBase{
private:
char *name ;
public:
Person() {
cout<<"Person()"<<endl ;
}
~Person() {
cout<<"~Person()"<<endl ;
}
void printInfo(void){ cout<<"just for test !"<<endl ; }
} ;
template <typename T>
class Sp {
private:
T *p ;
public:
/*使用Person *来构造*/
Sp(T *per){
cout<<"Sp(Person *per)"<<endl ;
p = per ;
p->incStrong();
}
/*拷贝构造函数*/
Sp(const Sp &other){
cout<<"Sp(Sp &other)"<<endl ;
p = other.p;
p->incStrong() ;
}
~Sp(){
cout<<"~Sp()"<<endl ;
if(p){
p->decStrong() ;
if(p->getStrong() == 0)
{
delete p ;
p = NULL ;
}
}
}
T * operator->(){
return p ;
}
T& operator*(){
return *p ;
}
} ;
int main(int argc , char** argv)
{
Sp<Person> s = new Person() ;
Sp<Person> s2 = s;
s->printInfo();
(*s).printInfo();
return 0 ;
}
总结:
我们可以使用智能指针代替平时的指针操作 ,使用智能指针来自动维护对象的生命周期。实际上,智能指针的目的就是防止在new操作后忘记delete操作从而造成内存泄漏,它仅仅是一种手段。Android源代码庞大且复杂,大量地采用智能指针来维护对象的生命周期,而在实际编程中,我们要时刻关注对象的生命周期,不应该依赖于这些手段而忽视对内存的重视,因为c/c++编程的本质就是在操作内存。
相关文章推荐
- C++智能指针,指针容器原理及简单实现(auto_ptr,scoped_ptr,ptr_vector).
- C++智能指针原理分析与简单实现
- C++智能指针,指针容器原理及简单实现(auto_ptr,scoped_ptr,ptr_vector).
- C++ 智能指针的简单实现
- 用c++简单实现智能指针(转)
- C++中智能指针的工作原理和简单实现
- C++面试题——智能指针的原理和实现
- [C/C++] 智能指针的实现及原理
- C++智能指针及其简单实现
- C++中智能指针的工作原理和简单实现
- 简单模拟实现c++智能指针-指针移交控制权
- C++智能指针的简单实现代码
- c++ 智能指针的简单实现
- 【C++】智能指针auto_ptr简单的实现
- 一个简单的C++智能指针的实现
- C++中智能指针的工作原理和简单实现
- 智能指针原理与简单实现(转)
- C++中智能指针的原理和简单使用
- C++中智能指针的工作原理和简单实现
- C++中智能指针的工作原理和简单实现