C++基础之单例模式自动释放-程序员宅基地

技术标签: c++  linux  C++学习笔记  多线程  


前言

个人学习笔记


       之前的介绍过关于单例模式的例子,其中对象是由_pInstance指针来保存,通过new创建的对象并没有进行释放,是因为单例模式之后没有其他代码需要执行,程序会立马结束,操作系统会自行回收相关资源,但对于后面有相关代码需要执行的程序来说,这种操作就会造成内存泄漏。有没有什么方式可以让对象自动释放?这样既可以避免内存泄漏,又可以让程序员不用关注对象释放的问题。在涉及到自动的问题时,我们很自然的可以想到:对象被销毁时,会自动调用其析构函数。利用这一特性,我们可以解决这一问题,这里介绍4种方式对单例模式进行自动释放:友元形式、内部类加静态数据成员形式、atexit形式、pthread_once形式。

一、友元形式

       创建一个新类AutoRelease,作为Singleton的友元,创建该类的栈对象,由于编译器会自动释放栈对象,在释放栈对象时会执行析构函数,这样执行析构函数的同时顺便把单例模式的对象给释放了,这样就可以达到目的了。
代码如下(示例):

#include <iostream>

using std::cout;
using std::endl;
class AutoRelease;

//1、友元形式
class Singleton
{
    
    friend AutoRelease;
public:
    static Singleton * getInstance()
    {
    
        if(nullptr == _pInstance)
        {
    
            _pInstance = new Singleton();
        }
        return _pInstance;
    }
    //不需要主动调用来释放对象了
    static void destroy()
    {
    
        if(_pInstance)
        {
    
            cout << "destroy()" << endl;
            delete _pInstance;
            _pInstance = nullptr;
        }
    }
private:
    Singleton()
    {
    
        cout << "Singleton()" << endl;
    }
    ~Singleton()
    {
    
        cout << "~Singleton()" << endl;
    }
private:
    static Singleton * _pInstance;
};
class AutoRelease
{
    
public:
    AutoRelease()
    {
    
        cout << "AutoRelease()" << endl;
    }
    ~AutoRelease()
    {
    
        cout << "~AutoRelease()" << endl;
        if(Singleton::_pInstance)//作为友元类,静态数据成员可以通过类名加作用域限定符获取
        {
    
            delete  Singleton::_pInstance;
            Singleton::_pInstance = nullptr;
        }
    }
};
Singleton *Singleton::_pInstance = nullptr;
int main()
{
    
    Singleton *s1 = Singleton::getInstance();
    Singleton *s2 = Singleton::getInstance();
    cout << "&s1 = " << s1 << endl;
    cout << "&s2 = " << s2 << endl;
    /* s1->destroy(); */
    /* s2->destroy(); */
    /* Singleton::destroy(); */
    AutoRelease ar;//创建栈对象,栈对象销毁的同时,顺便把Singeleton对象进行销毁,不用调用destroy函数,自动把单例对象释放
    return 0;
}

二、内部类+静态数据成员形式

       除了友元,还可以把AutoRelease作为Singleton的内部类,用public修饰时,可以直接在外面创建AutoRelease的栈对象,其作用原理与友元形式类似,因为AutoRelease只为Singleton服务,所以AutoRelease要用private修饰,用private修饰后,AutoRelease对象就不能在Singleton类外面创建,所以只能在Singleton类里面创建,作为Singleton的普通数据成员,在Singleton里创建AutoRelease对象时,会调用AutoRelease构造函数,而此时的AutoRelease对象并不是栈对象,所以不会自动执行析构函数,需要Singleton创建的类对象销毁时才会回收数据成员,这时才会执行AutoRelease的析构函数,进而再回收Singleton的对象,而Singleton创建的单例对象就是靠AutoRelease才能释放掉,这里就陷入了一个死循环,所以就不能创建AutoRelease对象作为Singleton的普通数据成员,也就是说Singleton对象不能拥有AutoRelease对象作为普通数据成员,所以需要把AutoRelease创建的对象从Singleton创建的对象中拿出来,放到全局/静态区的位置,也就是让AutoRelease创建的对象作为Singleton的静态数据成员,不仅仅被某个Singleton对象拥有,它是一个全局的,不受某一个Singleton对象控制,又因为处于全局/静态区的对象或变量会随着进程的结束而进行销毁,这时就会执行AutoRelease的析构函数,进而把_pInstance给delete掉,这时就会执行Singleton的析构函数,销毁释放Singleton单例模式创建的对象,最终达到目的。
代码如下(示例):

#include <iostream>

using std::cout;
using std::endl;
class AutoRelease;

//2、内部类 + 静态数据成员
class Singleton
{
    
public:
    static Singleton * getInstance()
    {
    
        if(nullptr == _pInstance)
        {
    
            _pInstance = new Singleton();
        }
        return _pInstance;
    }
    static void destroy()
    {
    
        if(_pInstance)
        {
    
            cout << "destroy()" << endl;
            delete _pInstance;
            _pInstance = nullptr;
        }
    }
private:
    class AutoRelease
    {
    
    public:
        AutoRelease()
        {
    
            cout << "AutoRelease()" << endl;
        }
        ~AutoRelease()
        {
    
            cout << "~AutoRelease()" << endl;
            if(_pInstance)//内部类可以直接使用_pInstance
            {
    
                delete  _pInstance;
                _pInstance = nullptr;
            }
        }
    };
private:
    Singleton()
    {
    
        cout << "Singleton()" << endl;
    }
    ~Singleton()
    {
    
        cout << "~Singleton()" << endl;
    }
private:
    static Singleton * _pInstance;
    static AutoRelease _ar;//不是栈对象
};
Singleton *Singleton::_pInstance = nullptr;
Singleton::AutoRelease Singleton::_ar;//放到全局静态的位置,编译器会自动赋一个初值
int main()
{
    
    Singleton *s1 = Singleton::getInstance();
    Singleton *s2 = Singleton::getInstance();
    cout << "&s1 = " << s1 << endl;
    cout << "&s2 = " << s2 << endl;
    /* Singleton::AutoRelease ar; */
    /* s1->destroy(); */
    /* s2->destroy(); */
    /* Singleton::destroy(); */
    cout << sizeof(Singleton) << endl;//=1
    return 0;
}

三、atexit形式

       这里需要借助一个C的函数atexit(),atexit函数会注册一个函数,注册的函数会在进程正常结束的时候调用,通过atexit注册多次就会调用多次。这时需要考虑atexit的位置,考虑到Singleton单例模式创建的对象只能被销毁一次,所以atexit()最好是放到单例模式创建的作用域内。但是在多线程环境下,上面的代码都是有缺陷的,因为if(nullptr==_pInstance)判断语句对于有几个线程的情况下,可能会同时满足if判断语句中的内容,然后创建多个Singleton对象,这样不仅不符合单例模式(一个类有且只能有一个对象),而且释放的时候不会把多线程创建的多个对象全部销毁释放掉,从而造成内存泄漏,这种情况下,对于多线程环境下是不安全的。出现这种情况是因为,在进入if判断语句之前,_pInstance的值为空,所以这里我们可以在_pInstance初始化的时候赋一个值,这样下次进入getInstance()时,_pInstance已经有值了,即使在多线程环境下,也不会创建多个对象,这样解决了多线程环境下不安全的问题,这就对应一种模式:饿汉模式。
代码如下(示例):

#include <stdlib.h>
#include <iostream>

using std::cout;
using std::endl;
class AutoRelease;

//3、atexit + 饿汉模式
//可以解决多线程环境下不安全的问题
class Singleton
{
    
public:
    static Singleton * getInstance()
    {
    
        if(nullptr == _pInstance)
        {
    
            _pInstance = new Singleton();
            atexit(destroy);
        }
        return _pInstance;
    }
    static void destroy()
    {
    
        cout << "destroy()" << endl;
        if(_pInstance)
        {
    
            delete _pInstance;
            _pInstance = nullptr;
        }
    }
private:
    Singleton()
    {
    
        cout << "Singleton()" << endl;
    }
    ~Singleton()
    {
    
        cout << "~Singleton()" << endl;
    }
private:
    static Singleton * _pInstance;
};
/* Singleton *Singleton::_pInstance = nullptr;//饱汉模式(懒汉模式) */
Singleton *Singleton::_pInstance = getInstance();//饿汉模式
int main()
{
    
    Singleton *s1 = Singleton::getInstance();
    Singleton *s2 = Singleton::getInstance();
    cout << "&s1 = " << s1 << endl;
    cout << "&s2 = " << s2 << endl;
    return 0;
}

四、pthread_once形式

       其实在linux系统中,有一个函数可以保证多线程环境下安全性的问题,它就是pthread_once(),完整形式:int pthread_once(pthread_once_t *once_control, void (*init_routine)(void)),该函数使用初值为PTHREAD_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅执行一次。在多线程编程环境下,尽管pthread_once()调用会出现在多个线程中,但init_routine()函数仅执行一次,究竟在哪个线程中执行是不定的,是由内核调度来决定。所以在这种情况下,即使在多线程环境下,选择饱汉模式或者饿汉模式都都可以,因为init_routine()函数只会执行一次。注意一点该形式只能在linux系统下使用,跨平台性不高。在linux系统通过g++编译的时候需要加上线程库-lpthread,其中这个p是POSIX,它是linux下的一个标准。
代码如下(示例):

#include <stdlib.h>
#include <pthread.h>
#include <iostream>

using std::cout;
using std::endl;
class AutoRelease;

//4、pthread_once形式
//可以解决多线程环境下不安全的问题
class Singleton
{
    
public:
    static Singleton * getInstance()
    {
    
    	//getInstance为静态函数,所以_once、init也必须设置为静态,否则在getInstance中不能访问
        pthread_once(&_once, init);//_once一旦初始化后,即使在多线程环境下,init()函数仅执行一次
        return _pInstance;
    }
    static void init()
    {
    
        _pInstance = new Singleton();
        atexit(destroy);
    }
    static void destroy()
    {
    
        cout << "destroy()" << endl;
        if(_pInstance)
        {
    
            delete _pInstance;
            _pInstance = nullptr;
        }
    }
private:
    Singleton()
    {
    
        cout << "Singleton()" << endl;
    }
    ~Singleton()
    {
    
        cout << "~Singleton()" << endl;
    }
private:
    static Singleton * _pInstance;
    static pthread_once_t _once;
};
Singleton *Singleton::_pInstance = nullptr;//饱汉模式(懒汉模式)
/* Singleton *Singleton::_pInstance = getInstance();//饿汉模式 */
pthread_once_t Singleton::_once = PTHREAD_ONCE_INIT;
int main()
{
    
    Singleton *s1 = Singleton::getInstance();
    Singleton *s2 = Singleton::getInstance();
    cout << "&s1 = " << s1 << endl;
    cout << "&s2 = " << s2 << endl;
    cout << PTHREAD_ONCE_INIT << endl;//=0
    return 0;
}

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u012344185/article/details/114495229

智能推荐

关于Android的UI非线程安全_android的ui控件是安全的吗-程序员宅基地

文章浏览阅读5.9k次。今天偶尔在网上看到这边文章 http://hold-on.iteye.com/blog/9914031、我们都知道,在Android的开发中,非UI线程不能操作UI线程中的控件,即UI是非线程安全的;2、但是在工作线程(非UI线程)中调用ProgressBar控件的setProgress(int count)方法时,程序运行并 不会出现异常,3、但是如调用setBackGround(int_android的ui控件是安全的吗

python - yeild-程序员宅基地

文章浏览阅读109次。带有yield的函数不仅仅只用于for循环中,而且可用于某个函数的参数,只要这个函数的参数允许迭代参数。比如array.extend函数,它的原型是array.extend(iterable)。带有 yield 的函数不再是一个普通函数,而是一个生成器generator,可用于迭代,工作原理同上。yield 是一个类似 return 的关键字,迭代一次遇到yield时就返回y..._py yeild

LaunchScreen启动图 黑屏_launchscreen 偶尔为大黑板-程序员宅基地

文章浏览阅读1.4k次。最近做项目,遇到了LaunchScreen欢迎页图片加载不上或者说不显示的问题。写个笔记记录一下。。。一开始一切都是顺利的,也可以显示欢迎页的图片,但是后来做别的功能时,图片突然不显示,试了好多方法,重新写LaunchScreen,或者换别的图片,但是都不显示,最后在网上看到别人说,是xcode的问题,我们的资源没有加载上,然后我就把测试机重启了,xcode也重启了,问题就解决了。_launchscreen 偶尔为大黑板

Qt架构图及模块分析介绍-程序员宅基地

文章浏览阅读405次。1、Qt框架图:2、Qt模块组成通用软件开发模块QtCore核心非图形接口类,为其他模块所调用QtGuiGUI(图形用户接口)功能模块QtMultimedia提供低级多媒体功能支持的类QtNetwork提供对网..._qt体系结构图

语义分割综述_语义分割后显示文字-程序员宅基地

文章浏览阅读1.9k次,点赞2次,收藏20次。前言本文对语义分割相关重要论文进行了简要概述,介绍了它们的主要改进方法和改进效果,并提供了这些论文的下载方式。本文来自公众号CV技术指南的技术总结系列点个关注 ,专注于计算机视觉的技术总结、最新技术跟踪、经典论文解读。语义分割 (Semantic segmentation) 是指将图像中的每个像素链接到类标签的过程。这些标签可能包括人、车、花、家具等。我们可以将语义分割视为像素级别的图像分类。例如,在有很多汽车的图像中,分割会将所有对象标记为汽车对象。然而,称为实例分割 (insta_语义分割后显示文字

web条形码+打印_web 条码 打印-程序员宅基地

文章浏览阅读869次。http://blog.csdn.net/zhaoqiliang527/article/details/4095873_web 条码 打印

随便推点

mysql的临时表不支持自连接-程序员宅基地

文章浏览阅读484次。TEMPORARY TABLE ProblemsThe following list indicates limitations on the use of TEMPORARY tables: A TEMPORARY table can only be of type MEMORY, ISAM, MyISAM, MERGE, or InnoDB. Temporary tables are not ..._mysql临时表自关联

题目:类Test1、类Test2定义如下:_类test1、test2定义如下: 1. public class test1 2.{ public-程序员宅基地

文章浏览阅读8.7k次,点赞2次,收藏5次。题目:类Test1、类Test2定义如下:public class Test1 {public float aMethod(float a, float b) throws IOException { }}public class Test2 extends Test1{}将以下哪种方法插入行6是不合法的。()A、float aMethod(float a,float b){}B、public int aMethod(int a,int b) throws Excepti_类test1、test2定义如下: 1. public class test1 2.{ public float amethod(float

unity开发:UDP socket网络连接_endpoint clientend-程序员宅基地

文章浏览阅读1.5w次,点赞8次,收藏40次。由于UDP是面向无连接的通信,所以实际上服务端和客户端是对等的,只不过服务端绑定了一个监听端口,而客户端每次都是自动分配的连接端口,其实也可以做成两边都绑定端口或者都不绑定端口的P2P模式。以下UDP是同步模式。建立两个unity工程,编写脚本,挂到场景中。服务端using UnityEngine;using System.Collections;//引入库u_endpoint clientend

ROS摄像机的标定(这里很好的一点就是给出了标定结果的各个参数的含义,这个很多都没讲)_distortion_coefficients-程序员宅基地

文章浏览阅读2.2k次。这里很好的一点就是给出了标定结果的各个参数的含义,这个很多都没讲转载自:https://blog.csdn.net/artista/article/details/51125560ROS摄像机的标定ArtistA 2016-04-11 21:06:07 8418 收藏 8 分类专栏: ROS本文主要为ROS camera_calibration 单目相机标定教程的翻译原文:http://wiki.ros.org/camera_calibration/Tutorials/Monocul_distortion_coefficients

HTML基础总结笔记_在html语言中,设置编号列表后整个项目列表包含在 和 标签之间,列表的每一项包-程序员宅基地

文章浏览阅读700次。HTML基础总结_在html语言中,设置编号列表后整个项目列表包含在 和 标签之间,列表的每一项包

Linux系统中源文件到软件的过程_this install program copies files (often just comp-程序员宅基地

文章浏览阅读210次。一、通过tartall归档文件安装(一)、基本步骤取得源文件:将tartall文件在/usr/local/src目录下解压缩;取得步骤流程:进入新建的目录下,区查阅INSTALL和README等相关文件;相关属性软件安装:根据INSTALL、README的内容说明安装好一些相关的软件(非必须);建立makefile:以自动检测程序(configure或autoconfig)检测环境变量..._this install program copies files (often just compiled) into destination loc