10.智能指针
10.1 unique_ptr
先看下面的代码:
#include <iostream>
using namespace std;
void Smart_pointer(int a);
int main(int argc, char ** argv)
{
Smart_pointer(1);
return 0;
}
void Smart_pointer(int a)
{
int * iptr = new int(a);
cout << "*iptr = " << *iptr << endl;
return;
}
当Smart_pointer函数执行完后,由于没有delete iptr(忘了写),因此iptr被销毁,而iptr指向的那块堆内存依旧存在,造成了内存泄漏。
注:当main函数执行完后,系统会自动清理堆空间;但是有的程序main函数会一直执行。
#include <iostream>
using namespace std;
void Smart_pointer(int a);
int main(int argc, char ** argv)
{
Smart_pointer(1);
return 0;
}
void Smart_pointer(int a)
{
int * iptr = new int(a);
cout << "*iptr = " << *iptr << endl;
//...发生异常...
delete iptr;
return;
}
当Smart_pointer函数未执行到delete处发生了异常,导致未释放堆空间造成内存泄漏。
注:在异常那一章讲解了这个问题,可以再catch和throw一次;但是不能每个函数都这么处理,这样太耗费资源。
#include <iostream>
using namespace std;
class SmartPointer {
private:
int * sp;
public:
SmartPointer():sp(NULL){}
SmartPointer(int * sp)
{
cout << "SmartPointer(int * sp)" << endl;
this->sp = sp;
}
~SmartPointer()
{
cout << "~SmartPointer()" << endl;
if (sp)
delete sp;
}
int operator*()
{
return *sp;
}
};
void Smart_pointer(int a);
int main(int argc, char ** argv)
{
Smart_pointer(1);
return 0;
}
void Smart_pointer(int a)
{
//int * iptr = new int(a);
//cout << "*iptr = " << *iptr << endl;
//delete iptr;
SmartPointer sp = new int(a);
cout << "*sp = " << *sp << endl;
return;
}
运行结果:
SmartPointer(int * sp)
*sp = 1
~SmartPointer()
执行了析构函数,使得sp被delete,即堆内存被释放。
为什么加上类SmartPointer 就可以了呢?
当执行SmartPointer sp = new int(a),实际分为2步:int * iptr = new int(a)和SmartPointer sp = iptr。当函数Smart_pointer执行完后,会销毁SmartPointer类的对象sp,调用sp类的析构函数,因此可以在析构函数中delete,这样会没有了内存泄漏问题。
其实在C++11的库中,提供一个智能指针模板unique_ptr,这样就不需要我们自己来提供智能指针。它需要包含头文件memory(被头文件iostream包含了)和使用名称空间std。
#include <iostream>
using namespace std;
void Smart_pointer(int a);
int main(int argc, char ** argv)
{
Smart_pointer(1);
return 0;
}
void Smart_pointer(int a)
{
unique_ptr<int> up_i(new int(a));
cout << " *up_i = " << *up_i << endl;
//不需要delete
return;
}
运行结果:
*up_i = 1
智能指针模板unique_ptr只能处理一个指针指向同一个对象的问题。如:
unique_ptr<int> up_i1(new int(a));
unique_ptr<int> up_i2;
up_i2 = up_i1;//编译出错
那么当多个指针指向同一个对象时,该怎么办呢?
10.2 shared_ptr
#include <iostream>
using namespace std;
class RefBase {
private:
int count;
public:
RefBase():count(0) {}
void incStrong() { count++; }
void decStrong() { count--; }
int getCount() { return count; }
};
template<typename T>
class SmartPointer {
private:
T * sp;
public:
SmartPointer() :sp(NULL) {}
SmartPointer(T * sp)
{
cout << "SmartPointer(T * sp)" << endl;
this->sp = sp;
sp->incStrong();
}
SmartPointer(const SmartPointer & c_sp)
{
cout << "SmartPointer(const SmartPointer & c_sp)" << endl;
this->sp = c_sp.sp;
this->sp->incStrong();
}
~SmartPointer()
{
cout << "~SmartPointer()" << endl;
if (sp)
{
cout <<"count = "<< sp->getCount() << endl;
sp->decStrong();
if(sp->getCount() == 0)
delete sp;
}
}
T operator*()
{
cout << "T operator*()" << endl;
return *sp;
}
};
class Student:public RefBase
{
private:
const char * name;
int age;
public:
Student() :name("no name"), age(0) { cout << "Student()" << endl; }
Student(const char * name, int age)
{
cout << "Student(const char * name, int gae)" << endl;
this->name = new char[strlen(name) + 1];
strcpy_s(const_cast<char *>(this->name), strlen(name) + 1, name);
this->age = age;
}
~Student()
{
if (this->name)
{
if (this->getCount() == 0)
{
cout << "~Student()" << endl;
delete this->name;
}
}
}
void pintf(void)
{
cout << "name: " << this->name << endl << "age:" << this->age << endl;
}
};
void test_sp(SmartPointer<Student>& a);
int main(int argc, char ** argv)
{
SmartPointer<Student> sp_stu = new Student("zhangsan",16);
test_sp(sp_stu);
return 0;
}
void test_sp(SmartPointer<Student>& a)
{
SmartPointer<Student> sp = a;
(*sp).pintf();
return;
}
运行结果:
Student(const char * name, int gae)
SmartPointer(T * sp)
SmartPointer(const SmartPointer & c_sp)
T operator*()
name: zhangsan
age:16
~SmartPointer()
count = 2
~SmartPointer()
count = 1
~Student()
在main函数中使用new创建的对象,有main()中的sp_stu和test_sp()中的sp指向它,共2个指针指向同一个对象。
我使用了类模板对上一个类SmartPointer进行了改进,使它更具有重用性。创建了基类RefBase进行引用计数,每引用一次count加1,析构一次count减1,当count为0时delete。
问题1:为什么拷贝构造函数中对count加1,加的却是原来Student对象的count?
因为这里的拷贝构造函数,仅仅是地址的复制,即是浅拷贝而不是深拷贝,它指向的还是原来的Student对象。
问题2:为什么要把引用计数放到RefBase中,而不是类SmartPointer中?
因为对于每一个对象都要有一个引用计数,它最好和实际对象绑定。若放在SmartPointer中,当test_sp执行完后,由于临时变量SmartPointer<Student> sp的销毁,导致调用Student的析构函数,delete掉数据成员name,最后运行失败。因此在Student的析构函数中增加对count的判断。
问题3:难道我们要自己提供和维护这样的RefBase类和SmartPointer类吗?有没有别的办法?
使用C++11库中的shared_ptr。它需要包含头文件memory(被头文件iostream包含了)和使用名称空间std。
#include <iostream>
using namespace std;
class RefBase {
private:
int count;
public:
RefBase():count(0) {}
void incStrong() { count++; }
void decStrong() { count--; }
int getCount() { return count; }
};
template<typename T>
class SmartPointer {
private:
T * sp;
public:
SmartPointer() :sp(NULL) {}
SmartPointer(T * sp)
{
cout << "SmartPointer(T * sp)" << endl;
this->sp = sp;
sp->incStrong();
}
SmartPointer(const SmartPointer & c_sp)
{
cout << "SmartPointer(const SmartPointer & c_sp)" << endl;
this->sp = c_sp.sp;
this->sp->incStrong();
}
~SmartPointer()
{
cout << "~SmartPointer()" << endl;
if (sp)
{
cout <<"count = "<< sp->getCount() << endl;
sp->decStrong();
if(sp->getCount() == 0)
delete sp;
}
}
T operator*()
{
cout << "T operator*()" << endl;
return *sp;
}
};
class Student:public RefBase
{
private:
const char * name;
int age;
public:
Student() :name("no name"), age(0) { cout << "Student()" << endl; }
Student(const char * name, int age)
{
cout << "Student(const char * name, int gae)" << endl;
this->name = new char[strlen(name) + 1];
strcpy_s(const_cast<char *>(this->name), strlen(name) + 1, name);
this->age = age;
}
~Student()
{
if (this->name)
{
if (this->getCount() == 0)
{
cout << "~Student()" << endl;
delete this->name;
}
}
}
void pintf(void)
{
cout << "name: " << this->name << endl << "age:" << this->age << endl;
}
};
void test_sp(shared_ptr<Student>& a);
int main(int argc, char ** argv)
{
//SmartPointer<Student> sp_stu = new Student("zhangsan",16);
shared_ptr<Student> sp_stu(new Student("zhangsan", 16));
test_sp(sp_stu);
return 0;
}
void test_sp(shared_ptr<Student>& a)
{
shared_ptr<Student> sp = a;
(*sp).pintf();
return;
}
运行结果:
Student(const char * name, int gae)
name: zhangsan
age:16
~Student()
智能指针模板shared_ptr适用于多个指针指向同一个对象的情形。
10.3 weak_ptr
#include <iostream>
using namespace std;
class Son;
class Father;
class Father
{
public:
shared_ptr<Son> son;
~Father()
{
cout << "~Father() " << endl;
}
};
class Son
{
public:
shared_ptr<Father> father;
~Son()
{
cout << "~Son()"<< endl;
}
};
void CrossReference(void);
int main()
{
cout<<"start of main..."<<endl;
CrossReference();
cout << "end of main..." << endl;
return 0;
}
void CrossReference(void)
{
shared_ptr<Father> f(new Father);
shared_ptr<Son> s(new Son);
// 交叉引用
f->son = s;
s->father = f;
cout << "类Father的引用计数:" << f.use_count() << endl;
cout << "类Son的引用计数:" << s.use_count() << endl;
return;
}
运行结果:
start of main...
类Father的引用计数:2
类Son的引用计数:2
end of main...
为什么没有调用类Father和类Son的析构函数?
在这之前,我们先了解什么是交叉引用。类A中对类B进行引用,则A对象要销毁则必须先销毁B对象;类B中对类A进行引用,则B对象要销毁则必须先销毁A对象。这样就造成了一个死锁,谁都销毁不了。类A中对类B进行引用,对象A可以决定对象B的生死,称之为强指针或强引用;A中对类B进行引用,对象A决定不了对象B的生死,称之为弱指针或弱引用。
对于上面代码中的交叉引用,可以用弱指针来解决。在C++11库中提供了弱指针weak_ptr。它需要包含头文件memory(被头文件iostream包含了)和使用名称空间std。
#include <iostream>
using namespace std;
class Son;
class Father;
class Father
{
public:
weak_ptr<Son> son;
~Father()
{
cout << "~Father() " << endl;
}
};
class Son
{
public:
shared_ptr<Father> father;
~Son()
{
cout << "~Son()"<< endl;
}
};
void CrossReference(void);
int main()
{
cout<<"start of main..."<<endl;
CrossReference();
cout << "end of main..." << endl;
return 0;
}
void CrossReference(void)
{
shared_ptr<Father> f(new Father);
shared_ptr<Son> s(new Son);
// 交叉引用
f->son = s;
s->father = f;
cout << "类Father的引用计数:" << f.use_count() << endl;
cout << "类Son的引用计数:" << s.use_count() << endl;
return;
}
运行结果:
start of main...
类Father的引用计数:2
类Son的引用计数:1
~Son()
~Father()
end of main...
只要交叉引用的其中一边使用weak_ptr就可以解开死锁。
在安卓的源代码中,提供了轻量级指针、强指针、弱指针对C++的对象进行回收,有兴趣的可以研究下它的原理。
https://blog.youkuaiyun.com/kc58236582/article/details/52279346这篇博客写的不错,大家可以看看。