本篇博文主要内容参考 C++的单例模式一文,在此,为原作者耐心细致的分析讲解,表示感谢。本文将结合此篇文章,给出自己做实验后的理解以及代码,作为今天学习的小结。

    单例模式,它的意图是保证一个类仅拥有一个实例,并在对外提供一个全局访问点,该实例被所有模块共享。这种模式的应用范围很广,比如系统日志输出,操作系统的窗口管理器,PC连接的键盘等等。

    单例模式是一种设计模式,它的具体实现和各种语言特性有关,这里主要介绍在C++上面的实现,测试平台为Win7 64位,VS2010开发环境。

    根据参考博文中的例子,在此先列举一下各种实现策略,以下均以CSingleton为类名来举例。

    1. 使用一个全局对象,比如就叫CSingleton g_instance,优点是访问方便,缺点是不能保证此类对象唯一,除了全局对象外,还能够创建CSingleton的局部实例。

    2. 使用类的静态成员变量,此变量为私有的静态成员指针,如static CSingleton1 *m_pInstance;此时,需要考虑让类自己在合适的时候释放掉此成员指针所指向的内容。

    3. 使用类的静态成员变量,此变量为私有的静态成员,如static CSingleton1 m_pInstance;

 

在给出代码前,要说明几个知识点:

     1. 类的静态成员(包括 成员变量和成员函数),属于类自身,所有实例对象均共享同一副本。

     2. 静态成员初始化操作在进入main函数之前,就已经分配空间并且完成初始化。静态成员变量必须在类体外初始化,通过类似 CSingleton1* CSingleton::m_pInstance = NULL的方式来定义初始化数值。如果不初始化,那么此成员就不会被分配空间,也就不会在类中存在。

     4. 静态成员在程序退出main函数后,会转到CRT类函数的清理中,完成程序静态变量、类的析构函数等资源释放的调用,具体细节就无需考虑,只需要知道退出main函数还需要做清理工作就行。

     3. 声明在类内部的类,成为嵌套类,它一般用来声明只在类内部使用的类。如果一定要在外部使用,需要加域解析符::

 

    好了,基本铺垫完成,开始码代码,先从简单开始。

    静态类变量方式,起先可以是类的成员变量,但需要在外部进行初始化,不优雅。其实,在类的静态成员函数中声明静态局部类变量,是一种更简洁的方法。

class CSingleton
{
private:
CSingleton() //构造函数是私有的
{
}
CSingleton(const CSingleton &);
CSingleton & operator = (const CSingleton &);
public:
static CSingleton & GetInstance()
{
static CSingleton instance; //局部静态变量
return instance;
}
};

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

    静态类指针方式:这种方式实现,因为有分配空间,所以需要考虑的东西比较多。参考C++的单例模式一文中的代码,下面给出经过亲身实践后,无内存泄露的代码。重要的地方,都写上了注释,便于大家理解。

// singlethon.cpp : 定义控制台应用程序的入口点。
#include <iostream>
#include <Windows.h>
using namespace std;
 
//用于开启CRT 内存泄露检测问题
#define _CRTDBG_MAP_ALLOC 
#include <stdlib.h> 
#include <crtdbg.h> 
 
//多线程保护锁类
class Lock
{
private:
    CRITICAL_SECTION  m_cs; // 封装临界区
    Lock(){};
    Lock(const Lock&){};                                            
    Lock& operator=(const Lock&){};
 
public:
    Lock(CRITICAL_SECTION cs):m_cs(cs)
    {
        InitializeCriticalSection(&m_cs);
    }
 
    void StartLock()
    {
        EnterCriticalSection(&m_cs);        
    }
 
    void StopLock()
    {
        // 离开临界区
        //LeaveCriticalSection(&m_cs);
    }
 
    ~Lock()
    {
        // 离开临界区 放在这里 貌似更好点
        LeaveCriticalSection(&m_cs);
 
        // 临界区不用的时候,进行销毁释放占用资源
        DeleteCriticalSection(&m_cs);
    }
};
 
//自己仿照参考,实现一个单例模式
class Singlethon
{
    //内嵌类,只能在Singlethon中使用,无法直接在外部使用
    class CGarbo    //唯一的作用,在析构函数中,删除Singlethon的实例
    {
    public:
        CGarbo()
        {
            cout << "constructor CGarbo"<<endl;
        }
        ~CGarbo()
        {
            if(Singlethon::m_pInstance)
            {
                cout << "execute CGarbo destructor function"<<endl;
                delete Singlethon::m_pInstance;
            }
        }
    };
 
private:
    Singlethon(){cout << "constructor Singlethon "<<endl;};    //禁止直接声明 Singlethon single;
    Singlethon(const Singlethon&){};                        //禁止间接声明 Singlethon single2(*(Singlethon::GetInstance()));
                                                            //      或者   Singlethon single2 = (*(Singlethon::GetInstance()));
    Singlethon& operator=(const Singlethon&){};        //禁止赋值操作     Singlethon single;single = *(Singlethon::GetInstance());
        
    static Singlethon* m_pInstance;
    static CGarbo Garbo;        //静态成员变量,程序结束时,系统自动调用它的析构函数
    static CRITICAL_SECTION cs;
 
public:
    static Singlethon* GetInstance();
 
    ~Singlethon()
    {
        cout << "execute Singlethon destructor function"<<endl;
        //不能在这里释放自身,因为CGarbox的析构函数会先于自身析构执行,而它在执行时,会调用 delete Singlethon::m_pInstance;
        //这会触发Singlethon自身的析构函数,而这里,再一次调用 delete Singlethon::m_pInstance,这就造成无限循环
        //最终会报错堆栈溢出的错误
        
    #if 0
        if(Singlethon::m_pInstance)            
            delete Singlethon::m_pInstance;
        Singlethon::m_pInstance = NULL;
    #endif 
    }
};
 
//以下三句话,缺一不可
Singlethon::CGarbo Singlethon::Garbo;
Singlethon* Singlethon::m_pInstance = NULL;
 
Singlethon* Singlethon::GetInstance()
{
    if (NULL == m_pInstance)
    {
        Lock lock(cs);
        lock.StartLock();
        if (NULL == m_pInstance)
        {
            m_pInstance = new Singlethon;
        }
        lock.StopLock();
    }    //正常退出或者异常抛出,这里都会自动调用lock的析构函数,因为lock的作用域是局部的
 
    return m_pInstance;
}
 
int main()
{
    //开启CRT内存泄露检测功能
    _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); 
 
    Singlethon* p1 = Singlethon::GetInstance();
    Singlethon* p2 = Singlethon::GetInstance();
    if ( p1 == p2)
    {
        cout << "单例模式测试成功!!"<<endl;
    }
    else
    {
        cout << "单例模式测试失败!!"<<endl;
    }
 
    return 0;
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

上述代码有几个地方要详细解析一下:

1. 在释放类的时候,如果类中有动态变量成员,一般要先释放其中的内容,然后在调用析构函数释放类本身的空间。类似的,比如在vector<int*> array,如果直接array.clear,其中,分配的内存空间就会泄露,正确的方法是,先释放掉分配的内容,然后清空空间。

for (vector<void *>::iterator it = v.begin(); it != v.end(); it ++)
if (NULL != *it)
{
delete *it;
*it = NULL;
}
v.clear();

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

2. 上述代码定义了一个锁类,封装了临界区的相关操作,作为资源管理类,内部有临界区变量,作为多线程安全的保证。局部资源管理类变量使得,当异常发生时,也能调用析构函数,释放临界区资源

3. 在进行判断时,判断了两次,提高效率。因为该方法调用第一次就产生实例,而pInstance == NULL 大部分情况下都为false,如果只判断一次,那么每次获取实例前都需要加锁,效率太低。

4. 定义一个嵌套类,和对应的静态局部变量,这样,当整个单例释放之前,就可以通过它来找到单例指针,然后delete掉他,这样就不会有内存泄露发送了。

5. 使用CRT类函数的内存泄露检测功能,方便查看调试。

    文章的最后,给出测试结果图:

   

    OK!

    读者可以在各个构造和析构函数中间加上端点,也可以尝试注释掉CGarbo的析构函数调试,看看有没有内存泄露。

    好久没有更新文章,这段时间工作很忙,忙不可怕,可怕的是没有目的、没用动机的忙。

 

    版权声明:本文为博主原创文章,欢迎转载,转载请注明出处,谢谢。

单例模式简介以及C++版本的实现的更多相关文章

  1. Linux简介与厂商版本

    Linux简介与厂商版本   作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 1. Linux简介 Linux可以有狭义和广义两种 ...

  2. Linux简介与厂商版本上

    Linux简介与厂商版本   作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 1. Linux简介 Linux可以有狭义和广义两种 ...

  3. Checkstyle 简介 以及各版本下载地址

    CheckStyle是SourceForge下的一个项目,提供了一个帮助JAVA开发人员遵守某些编码规范的工具.它能够自动化代码规范检查过程,从而使得开发人员从这项重要,但是枯燥的任务中解脱出来. C ...

  4. HTML data属性简介以及低版本浏览器兼容算法

    实例 使用 data-* 属性来嵌入自定义数据: <ul> <li data-animal-type="bird">Owl</li> <l ...

  5. C# 单例模式Lazy<T>实现版本

    非Lazy版本的普通单例实现: public sealed class SingletonClass : ISingleton { private SingletonClass () { // the ...

  6. Linux简介与厂商版本下

    2. Linux的厂商版本 在Linux内核基础上,我们还有许多厂商版本.即使有了内核和GNU软件,Linux的安装和编译并不是简单的工作,Linux厂商就是瞄准了这个市场.这些厂商会在Linux内核 ...

  7. 微信分享网页时自定义缩略图和简介(.net版本)

    要实现微信分享网页时自定义缩略图和简介,需开发者在公众平台网站中创建公众号.获取接口权限后,通过微信JS-SDK的分享接口,来实现微信分享功能. 下面来说明实现步骤. 第一部分 准备步骤 步骤一:注册 ...

  8. MYSQL—第一部分(简介和windows版本的安装)

    一.概述 1.什么是数据库 ? 答:数据的仓库,如:在自己编写的程序中我们创建了一个 db 目录,称其为数据库 2.什么是 MySQL.Oracle.SQLite.Access.MS SQLServe ...

  9. 基于ZigBee模块与51单片机之间的简化智能家居项目简介(学生版本)

    5月份学校举行比赛,我们团队报名<智能家居>的项目,设计的总体思路用:QT写的上位机与ZigBee无线通信加51作为终端的简易版智能家居 电路连接:PC机->cc2530(协调器)- ...

随机推荐

  1. 插入中国所有省和市的SQL语句--以后用

    需要做根据省市过滤查询的,不得已写入数据库,这个留着方便以后用. 首先创建两张表 drop database if exists BSM; create database BSM; use BSM; ...

  2. laravel 5.4在控制器构造函数中获取auth中间件失败

    想在控制器的构造函数中取出登录的用户 ,保存到类的属性中. 当然可以用Auth::user(),可以做到,但是不想这么做. 没想到这个属性一直是空的.后来用xdebug调试,在中间件handle和构造 ...

  3. 神经网络 之 DNN(深度神经网络) 介绍

    CNN(卷积神经网络).RNN(循环神经网络).DNN(深度神经网络) CNN 专门解决图像问题的,可用把它看作特征提取层,放在输入层上,最后用MLP 做分类. RNN 专门解决时间序列问题的,用来提 ...

  4. Hbase 学习(七) rowkey设计

    一直以来对rowkey的设计都比较迷茫,<hbase权威指南>倒是给出了个还算靠谱的例子. 下面这个例子有点儿像帖子表结构,它的rowkey设计是这样的,可以简单的理解为,什么人在什么时间 ...

  5. python 获取前一天凌晨unix时间

    # -*- coding:utf-8 -*-import time now_time = int(time.time())day_time = now_time - now_time % 86400 ...

  6. JS 二维数组排序

    <script> //测试方法 var a = [ {name:'hdj', age:128}, {name:'hdj1', age:28}, {name:'hdj1', age:78}, ...

  7. Android studio 3+版本apk安装失败问题

    studio2.3升级到3.1之后将apk发给别人下载到手机上安装,华为提示安装包无效或与操作系统不兼容,魅族提示apk仅为测试版,要求下载正式版安装. 在网上找了一下,发现是studio3.0之后的 ...

  8. 让网站和APP更具动感的几点建议

    [编者按]本文来自Smashing Magazine,作者为Brolik的联合创始人.首席创意官Drew Thomas.文中介绍了Web中增加动感设计可带来的好处及进行动感设计的几点建议,如采用图层技 ...

  9. What Great .NET Developers Ought To Know (More .NET Interview Questions)

    A while back, I posted a list of ASP.NET Interview Questions. Conventional wisdom was split, with ab ...

  10. 前段时间,接手一个项目使用的是原始的jdbc作为数据库的访问,发布到服务器上在运行了一段时间之后总是会出现无法访问的情况,登录到服务器,查看tomcat日志发现总是报如下的错误。    Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Data source rejected est

    前段时间,接手一个项目使用的是原始的jdbc作为数据库的访问,发布到服务器上在运行了一段时间之后总是会出现无法访问的情况,登录到服务器,查看tomcat日志发现总是报如下的错误. Caused by: ...