• 3.7 C++高级编程_自己实现智能指针


    指针未释放引起的内存泄漏

    先定义一个Person类,在Person类中定义一个构造函数,一个析构函数,一个普通的成员函数。

    Person类

    1. class Person {
    2. public:
    3. Person()
    4. {
    5. cout << "Person()" << endl;
    6. }
    7. ~Person()
    8. {
    9. cout << "~Person()" << endl;
    10. }
    11. void printInfo(void)
    12. {
    13. cout << "printInfo()" << endl;
    14. }
    15. };

    然后编写一个测试函数test_func,从内存中申请了一块Person类大小的空间。

    test_func函数

    1. void test_func(void)
    2. {
    3. Person *p = new Person();
    4. p->printInfo();
    5. }

    最后,在main函数中调用test_func函数。

    main函数

    1. int main(int argc, char **argv)
    2. {
    3. test_func();
    4. return 0;
    5. }

    最终,函数源码如下:

    1. #include <iostream>
    2. #include <string.h>
    3. #include <unistd.h>
    4. using namespace std;
    5. class Person {
    6. public:
    7. Person()
    8. {
    9. cout << "Person()" << endl;
    10. }
    11. ~Person()
    12. {
    13. cout << "~Person()" << endl;
    14. }
    15. void printInfo(void)
    16. {
    17. cout << "printInfo()" << endl;
    18. }
    19. };
    20. void test_func(void)
    21. {
    22. Person *p = new Person();
    23. p->printInfo();
    24. }
    25. int main(int argc, char **argv)
    26. {
    27. test_func();
    28. return 0;
    29. }

    这个代码有什么问题呢?

    我们编译执行一下,可以看到Person类的构造函数被调用了,但是在程序运行完毕退出后Person类的析构函数没有调用

    也就是说,在程序运行退出后,没有将之前申请的内存释放掉,这时候,就发生了内存泄漏

    增加内存释放

    要想防止这个问题,可以这样做:

    1、在函数退出时增加delete操作

     

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

    今天讲第 3 种方法,那就是使用智能指针

    智能指针

    定义一个sp类,在sp类中,有一个私有成员p,它是一个指向Person类的指针

    然后定义两个构造函数,一个构造函数的传参为一个指向Person类的指针,另一个构造函数的传参为,主要是在没有申请Person类空间的时候将私有成员指空

    定义一个析构函数,在析构函数中,如果指针p不为空,则表示有申请过内存空间,此时需要将空间释放。

    1. class sp {
    2. private:
    3. Person* p;
    4. public:
    5. sp () : p(0)
    6. {
    7. cout << "sp () : p(0)" << endl;
    8. }
    9. sp (Person *other)
    10. {
    11. cout << "sp (Person *other)" << endl;
    12. this->p = other;
    13. }
    14. ~sp ()
    15. {
    16. cout << "~sp ()" << endl;
    17. if (p) {
    18. cout << "delete this->p" << endl;
    19. delete this->p;
    20. }
    21. }
    22. };

    修改test_func函数,在函数中创建了一个sp类的对象s。

    1. void test_func(void)
    2. {
    3. sp s = new Person;
    4. }

    此时,会调用构造函数 sp (Person *other)。

    编译测试,可以看到虽然没有delete,但是申请的内存空间在函数调用结束后,还是会自动释放。

    这是因为创建的对象p实际上是一个局部变量,局部变量的生命周期随函数调用结束而终止

    重定向->

    此时,如果想要使用Person类里面的成员要怎么做?

    显然直接使用“->”是不行的,因为 s 实际上还是一个sp类型的对象。

    此时需要重定向“->”,在sp类的定义中添加下面的重定向函数,返回sp类中指向Person类的指针成员。

    1. Person* operator->(void)
    2. {
    3. return this->p;
    4. }

    修改test_func函数,增加调用Person类的成员函数printInfo。

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

    增加传参为sp类本身的构造函数

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

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

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

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

    报错信息说:不可以把sp转换为sp引用。

    先分析一下第71行,实际上,编译第71行的代码时,编译器实际上是通过三行代码实现的:

    1. sp other = new Person();
    2. 相当于:
    3. Person *p = new Person();
    4. sp tmp(p); ==> 对应的构造函数:sp (Person *other)
    5. 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行报了类型错误,费解...

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

    1. test_func函数,退出函数,清除临时变量时调用了一次;
    2. 程序执行完毕退出时,又调用了一次;

     

    当空间已经释放后,不应该再对这个空间进行二次释放。

    修改代码,在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。

    1. template <typename T>
    2. class sp {
    3. private:
    4. T* p;
    5. public:
    6. sp () : p(0)
    7. {
    8. cout << "sp () : p(0)" << endl;
    9. }
    10. sp (T *other)
    11. {
    12. cout << "sp (T *other)" << endl;
    13. this->p = other;
    14. p->incStrong();
    15. }
    16. sp (const sp &other)
    17. {
    18. cout << "sp (const sp &other)" << endl;
    19. this->p = other.p;
    20. p->incStrong();
    21. }
    22. ~sp ()
    23. {
    24. cout << "~sp ()" << endl;
    25. if (p) {
    26. cout << "delete this->p" << endl;
    27. p->decStrong();
    28. if (0 == p->getStrongCount()) {
    29. delete this->p;
    30. this->p = NULL;
    31. }
    32. }
    33. }
    34. T* operator->(void)
    35. {
    36. return this->p;
    37. }
    38. T& operator*(void)
    39. {
    40. return *p;
    41. }
    42. };

    测试函数也修改为模板函数。

    1. template <typename T>
    2. void test_func(sp<T> &other)
    3. {
    4. sp<T> s = other;
    5. s->printInfo();
    6. }

    main函数中,创建sp对象时,将T声明为Person。

    编译测试,执行成功。

    这样,以后如果需要使用Person *时,就可以使用sp来代替。

    此时,new出来了一个对象,但是不需要去delete它,由系统来自动delete这个对象。

  • 相关阅读:
    C++ Builder XE 关于RichEdit如何设置文本中某个字符颜色
    nodejs毕业设计源码丨基于微信小程序的家政服务系统
    c++视觉处理---霍夫变换
    基于Pandas+余弦相似度+大数据智能护肤品推荐系统——机器学习算法应用(含Python工程源码)+数据集
    半导体聚合物纳米颗粒,PSQPNs-DBCO点击化学, 二苯并环辛炔-PSQPNs
    文件的操作
    windows查看连接过wifi的密码
    DN-DETR源码讲解
    c#手动签字实现
    Linux软件包管理— rpm软件包查询
  • 原文地址:https://blog.csdn.net/qq_33141353/article/details/126338393