先定义一个Person类,在Person类中定义一个构造函数,一个析构函数,一个普通的成员函数。
Person类
- class Person {
- public:
- Person()
- {
- cout << "Person()" << endl;
- }
-
- ~Person()
- {
- cout << "~Person()" << endl;
- }
-
- void printInfo(void)
- {
- cout << "printInfo()" << endl;
- }
- };
然后编写一个测试函数test_func,从内存中申请了一块Person类大小的空间。
test_func函数
- void test_func(void)
- {
- Person *p = new Person();
-
- p->printInfo();
- }
最后,在main函数中调用test_func函数。
main函数
- int main(int argc, char **argv)
- {
- test_func();
-
- return 0;
- }
最终,函数源码如下:
- #include <iostream>
- #include <string.h>
- #include <unistd.h>
-
- using namespace std;
-
- class Person {
- public:
- Person()
- {
- cout << "Person()" << endl;
- }
-
- ~Person()
- {
- cout << "~Person()" << endl;
- }
-
- void printInfo(void)
- {
- cout << "printInfo()" << endl;
- }
- };
-
- void test_func(void)
- {
- Person *p = new Person();
-
- p->printInfo();
- }
-
- int main(int argc, char **argv)
- {
- test_func();
-
- return 0;
- }
这个代码有什么问题呢?
我们编译执行一下,可以看到Person类的构造函数被调用了,但是在程序运行完毕退出后,Person类的析构函数没有调用。
也就是说,在程序运行退出后,没有将之前申请的内存释放掉,这时候,就发生了内存泄漏。

要想防止这个问题,可以这样做:
1、在函数退出时增加delete操作。

2、或者使用局部变量也可以防止这个问题。


今天讲第 3 种方法,那就是使用智能指针。
定义一个sp类,在sp类中,有一个私有成员p,它是一个指向Person类的指针。
然后定义两个构造函数,一个构造函数的传参为一个指向Person类的指针,另一个构造函数的传参为空,主要是在没有申请Person类空间的时候将私有成员指空。
定义一个析构函数,在析构函数中,如果指针p不为空,则表示有申请过内存空间,此时需要将空间释放。
- class sp {
- private:
- Person* p;
-
- public:
- sp () : p(0)
- {
- cout << "sp () : p(0)" << endl;
- }
-
- sp (Person *other)
- {
- cout << "sp (Person *other)" << endl;
- this->p = other;
- }
-
- ~sp ()
- {
- cout << "~sp ()" << endl;
- if (p) {
- cout << "delete this->p" << endl;
- delete this->p;
- }
- }
- };
修改test_func函数,在函数中创建了一个sp类的对象s。
- void test_func(void)
- {
- sp s = new Person;
- }
此时,会调用构造函数 sp (Person *other)。
编译测试,可以看到虽然没有delete,但是申请的内存空间在函数调用结束后,还是会自动释放。
这是因为创建的对象p实际上是一个局部变量,局部变量的生命周期随函数调用结束而终止。

此时,如果想要使用Person类里面的成员要怎么做?
显然直接使用“->”是不行的,因为 s 实际上还是一个sp类型的对象。
此时需要重定向“->”,在sp类的定义中添加下面的重定向函数,返回sp类中指向Person类的指针成员。
- Person* operator->(void)
- {
- return this->p;
- }
修改test_func函数,增加调用Person类的成员函数printInfo。

编译测试,可以看到成员函数printInfo被成功调用。

继续修改代码,增加一个拷贝构造函数,传参为sp类本身。

修改测试函数,传入一个sp类对象,使用传入的对象,初始化函数内部定义的局部变量。

修改main函数,创建一个sp类对象,并且把创建的对象作为参数传给test_func函数。

编译测试,发现第71行有报错。

报错信息说:不可以把sp转换为sp引用。
先分析一下第71行,实际上,编译第71行的代码时,编译器实际上是通过三行代码实现的:
- sp other = new Person();
- 相当于:
- Person *p = new Person();
- sp tmp(p); ==> 对应的构造函数:sp (Person *other)
- sp other(tmp); ==> 对应的构造函数:sp(sp &other)
问题出在第三行:sp &other = tmp; 这是错误的语法,不能引用临时变量。
修改为:const sp &other = tmp; 正确的语法。
修改构造函数,增加const属性。

此时可以编译成功,但是执行时有报错。
这里其实有一个疑惑。
我的理解是sp other = new Person(); 这行代码其实传入的就是一个指向Person类的指针,调用的应该就是sp (Person *other),没有用到sp (const sp &other)才对;
用到sp (const sp &other)函数的,应该是 sp s = other;
从执行来看也是sp other = new Person(); 也是没有调用到 sp (const sp &other) 的,但是编译器确实在71行报了类型错误,费解...

显然,报错的原因是申请的空间被释放了两次:



当空间已经释放后,不应该再对这个空间进行二次释放。
修改代码,在Person类中增加一个私有成员count,用来记录sp类的使用次数,当这个次数不为0时,就表示没有其他程序使用该对象,此时可以将申请的空间释放掉。

在Person类的构造函数中将计数值初始化为0。

在sp类的构造函数中对计数值加1。

在析构函数中对计数值减1,当减为0时,表示没有其他程序要操作这个类,此时才调用delete释放空间。

编译测试,程序可以正常执行。

使用智能指针的目的是:少用"Person *"; 用"sp"来代替"Person *"。
防止出现内存未释放引起的内存泄漏。
Person *per,有2种操作:per->xxx, (*per).xxx;
那么,sp也应该有这两种操作:sp->xxx, (*sp).xxx;
其中,sp->xxx之前已经实现了,现在来增加 (*sp).xxx。
修改sp类,增加一个成员函数 Person& operator*(void),重定向*。

修改main函数,测试一下。

程序可以正常执行。

前面为了解决空间重复释放的问题,我们在Person类中加了一个count变量,还有与这个变量相关的一系列操作函数。
这部分的内容,如果定义了一个Cat类,Dog类时,也有可能会使用。
那么,可以把这部分内容单独拿出来,单独定义一个基类,后续如果有其他类需要使用计数的功能时,就可以直接继承这个基类。
修改代码,增加一个基类RefBase。
需要注意的是,要将之前在Person类的构造函数初始化count值,改为在RefBase类的构造函数初始化count值。

Person类继承基类RefBase即可。

编译测试,程序执行成功。


对于当前的代码,可以使用sp代替Person *p,但是如果有Cat类,或者Dog类,要使用sp代替Cat *c,Dog *d,还需要重新定义sp类。
但是,如果将sp类定义为一个模板,则可以免去重新定义的繁琐操作。
将sp类修改为模板,将之前sp类中的Person全部换成T。
- template <typename T>
- class sp {
- private:
- T* p;
- public:
- sp () : p(0)
- {
- cout << "sp () : p(0)" << endl;
- }
-
- sp (T *other)
- {
- cout << "sp (T *other)" << endl;
- this->p = other;
- p->incStrong();
- }
-
- sp (const sp &other)
- {
- cout << "sp (const sp &other)" << endl;
- this->p = other.p;
- p->incStrong();
- }
-
- ~sp ()
- {
- cout << "~sp ()" << endl;
- if (p) {
- cout << "delete this->p" << endl;
- p->decStrong();
- if (0 == p->getStrongCount()) {
- delete this->p;
- this->p = NULL;
- }
- }
- }
-
- T* operator->(void)
- {
- return this->p;
- }
-
- T& operator*(void)
- {
- return *p;
- }
- };
测试函数也修改为模板函数。
- template <typename T>
- void test_func(sp<T> &other)
- {
- sp<T> s = other;
-
- s->printInfo();
- }
main函数中,创建sp对象时,将T声明为Person。

编译测试,执行成功。

这样,以后如果需要使用Person *时,就可以使用sp
此时,new出来了一个对象,但是不需要去delete它,由系统来自动delete这个对象。