基础知识掌握:

单例考虑三点:内存何时释放、运行速度如何、多线程下能否保证只有一个实例

如果获取对象的返回值类型是引用,返回值赋值给变量而不是引用会进行对象的拷贝,这样就会出现两个对象,可以把显示声明拷贝构造函数(包括 = 操作符)为private,这样就不会进行对象的拷贝

如果获取对象的返回值是指针,一方面需要创建一个指针接收返回的对象指针,另一方面可能误执行delete把这个对象销毁了,此对象的销毁推荐设计在程序结束后自行销毁。

程序在结束时会自动回收(析构)全局作用范围内的变量(全局变量和静态变量),但是new出来的不由程序回收,可能通过系统回收,系统也可能不回收。

静态变量的内存分配和初始化

全局变量、non-local static变量(文件域的静态变量和类的静态成员变量)在main执行之前的静态初始化过程中分配内存并初始化;local static 变量(局部静态变量)则是编译时分配内存,第一次使用时初始化。这里的变量包含内置数据类型和自定义类型的对象。

静态变量初始化的线程安全性说明

1、非局部静态变量一般在main执行之前的静态初始化过程中分配内存并初始化,可以认为是线程安全的;
2、局部静态变量在编译时,编译器的实现一般是在初始化语句之前设置一个局部静态变量的标识来判断是否已经初始化,运行的时候每次进行判断,如果需要初始化则执行初始化操作,否则不执行。这个过程本身不是线程安全的。C++0x之后该实现是线程安全的。

  C++11标准针规定了局部静态变量初始化需要保证线程安全,C++03标准并无此说明,具体说明如下:

    If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization

单例运用场景:

设计模式经典GoF定义的单例模式需要满足以下两个条件:

1)保证一个类只创建一个实例;
2)提供对该实例的全局访问点。

如果系统有类似的实体(有且只有一个,且需要全局访问),那么就可以将其实现为一个单例。实际工作中常见的应用举例:

1)日志类,一个应用往往只对应一个日志实例。
2)配置类,应用的配置集中管理,并提供全局访问。
3)管理器,比如windows系统的任务管理器就是一个例子,总是只有一个管理器的实例。
4)共享资源类,加载资源需要较长时间,使用单例可以避免重复加载资源,并被多个地方共享访问。

单例实现:

Lazy Singleton(懒汉模式)

首先看GoF在描述单例模式时提出的一种实现,教科书式的例子。

//头文件
class Singleton
{
public:
static Singleton& Instance() //Instance()作为静态成员函数提供里全局访问点
{
if(ps == NULL) //如果还未实例化,即可实例化,反之提供实例的引用
ps = new Singleton;
return *ps; //返回指针的话可能会误被 delete,返回引用安全一点
} private:
Singleton(); //这里将构造,析构,拷贝构造,赋值函数设为私有,杜绝了生成新例
~Singleton();
Singleton(const Singleton&);
Singleton& operator=(const Singleton&); static Singleton* ps;
}; //源文件
Singleton* Singleton::ps = NULL;

这种方法的好处在于直到 Instance() 被访问,才会生成实例,这种特性被称为延迟初始化(Lazy Initialization),这在一些初始化时消耗较大的情况有很大优势。

Lazy Singleton不是线程安全的,比如现在有线程A和线程B,都通过了 ps == NULL 的判断,那么线程A和B都会创建新实例。单例模式保证生成唯一实例的规则被打破了。

Eager Singleton(饿汉模式)

这种实现在编译器初始化的时候就完成了实例的创建,和上述的Lazy Singleton相反。

//头文件
class Singleton
{
public:
static Singleton& Instance() //Instance()作为静态成员函数提供里全局访问点
{
return instance;
} private:
Singleton(); //这里将构造,析构,拷贝构造,赋值函数设为私有,杜绝了生成新例
~Singleton();
Singleton(const Singleton&);
Singleton& operator=(const Singleton&); static Singleton instance;
}; //源文件
Singleton Singleton::instance;

由于实例化是在初始化阶段执行的,所以没有线程安全的问题,但是潜在问题在于no-local static对象(函数外的static对象)在不同编译单元(可理解为cpp文件和其包含的头文件)中的初始化顺序是未定义的。如果在初始化完成之前调用 Instance()方法会返回一个未定义的实例。例如有两个单例 SingletonA 和 SingletonB ,都采用了 Eager Initialization ,那么如果 SingletonA 的初始化需要 SingletonB ,而这两个单例又在不同的编译单元,初始化顺序是不定的,如果 SingletonA 在 SingletonB 之前初始化,就会出错,以下举例说明:

实例:ASingleton、BSingleton两个单例类,其中 ASingleton 的构造函数中使用到 BSingleton 的单例对象。

class ASingleton
{
public:
static ASingleton* getInstance()
{
return &m_data;
}
void do_something()
{
cout<<"ASingleton do_something!"<<endl;
}
protected:
static ASingleton m_data; //static data member 在类中声明,在类外定义
ASingleton();
~ASingleton() {}
}; class BSingleton
{
public:
static BSingleton* getInstance()
{
return &m_data;
}
void do_something()
{
cout<<"BSingleton do_something!"<<endl;
}
protected:
static BSingleton m_data; //static data member 在类中声明,在类外定义
BSingleton();
~BSingleton() {}
}; ASingleton ASingleton::m_data;
BSingleton BSingleton::m_data; ASingleton::ASingleton()
{
cout<<"ASingleton constructor!"<<endl;
BSingleton::getInstance()->do_something();
} BSingleton::BSingleton()
{
cout<<"BSingleton constructor!"<<endl;
}

在这个测试例子中,我们将上述代码放在一个 main.cpp 文件中,其中 main 函数为空。

int main()
{
return ;
}

运行测试结果是:

ASingleton constructor!
BSingleton do_something!
BSingleton constructor!

BSingleton 的构造函数居然是在成员函数 do_something 之后调用的!

Meyers Singleton

为了解决上面的问题,Scott Meyers在《Effective C++》(Item 04)中的提出另一种更优雅的单例模式实现,使用local static对象(函数内的static对象)。当第一次访问 Instance() 方法时才创建实例。

//头文件
class Singleton
{
public:
static Singleton& Instance() //Instance()作为静态成员函数提供里全局访问点
{
static Singleton instance;
return instance;
} private:
Singleton(); //这里将构造,析构,拷贝构造,赋值函数设为私有,杜绝了生成新例
~Singleton();
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
};

C++0x之后该实现是线程安全的,有兴趣可以读相关的标准草案(section 6.7),编译器的支持程度不一定,但是G++4.0及以上是支持的。

这里推荐一个饿汉式单例,不要求局部静态变量线程安全,并且避免了初始化顺序问题。boost 的实现方式是:单例对象作为静态局部变量,然后增加一个辅助类,并声明一个该辅助类的类静态成员变量,在该辅助类的构造函数中,初始化单例对象。

class Singleton
{
public:
static Singleton* getInstance()
{
static Singleton instance;
return &instance;
} protected:
struct Object_Creator
{
Object_Creator()
{
cout<<"Object_Creator constructor"<<endl;
Singleton::getInstance();
}
};
static Object_Creator _object_creator; Singleton() {cout<<"Singleton constructor"<<endl;}
~Singleton() {}
};
Singleton::Object_Creator Singleton::_object_creator;

Double-Checked Locking Pattern(双检测锁模式)

回顾 Lazy Singleton 模式,考虑到线程安全,我们可以通过加锁来保护单例初始化这一过程,双检测锁模式就是在懒汉模式的基础上稍作修改得到:

//头文件
class Singleton
{
public:
static Singleton& Instance() //Instance()作为静态成员函数提供里全局访问点
{
if(ps == NULL)
{
Lock(); //上锁
if(ps == NULL) //如果还未实例化,即可实例话,反之提供实例的引用
ps = new Singleton;
Unlock(); //解锁
}
return *ps; //返回指针的话可能会误被 delete,返回引用安全一点
} private:
Singleton(); //这里将构造,析构,拷贝构造,赋值函数设为私有,杜绝了生成新例
~Singleton();
Singleton(const Singleton&);
Singleton& operator=(const Singleton&); static Singleton* ps;
}; //源文件
Singleton* Singleton::ps = NULL;

以上的上锁和解锁仅用于说明,实际应用中可以使用互斥锁,单一信号量等方法去实现。

这里的两次 ps == NULL,是借鉴了Java的单例模式实现时,使用的所谓的“双检锁模式”(Double-Checked Locking Pattern)。因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,同时也保证了线程安全。理论上问题解决了,但是在实践中有很多坑,如指令重排、多核处理器等问题让DCLP实现起来比较复杂比如需要使用内存屏障,详细的分析可以阅读这篇论文《C++ and the Perils of Double-Checked Locking》

在C++11中有全新的内存模型和原子库,可以很方便的用来实现DCLP。这里不展开。有兴趣可以阅读这篇文章《Double-Checked Locking is Fixed In C++11》

pthread_once

在多线程编程环境下,尽管 pthread_once() 调用会出现在多个线程中,init_routine()函数仅执行一次,pthread_once是很适合用来实现线程安全单例。(pthread_once 在一个进程里只会执行一次,其实现方式使用的就是互斥锁+条件变量的方法)

template<typename T>
class Singleton : boost::noncopyable
{
public:
static T& instance()
{
pthread_once(&ponce_, &Singleton::init);
return *value_;
} static void init()
{
value_ = new T();
}
private:
static pthread_once_t ponce_;
static T* value_;
};
template<typename T>
pthread_once_t Singleton<T>::ponce_ = PTHREAD_ONCE_INIT; template<typename T>
T* Singleton<T>::value_ = NULL;

这里的boost::noncopyable的作用是把构造函数, 赋值函数, 析构函数, 复制构造函数声明为私有或者保护。

不使用模版,如下:

//头文件
pthread_once_t once = PTHREAD_ONCE_INIT;
class Singleton
{
public:
static Singleton& Instance() //Instance()作为静态成员函数提供一次实例化以及全局访问点
{
pthread_once(&once, &Init);
return *ps;
} static void Init()
{
ps = new Singleton;
} private:
Singleton();
~Singleton();
Singleton(const Singleton&);
Singleton& operator=(const Singleton&); static Singleton* ps;
};
//源文件
Singleton* Singleton::ps = NULL;

单例 ------ C++实现的更多相关文章

  1. java单例设计模式

    单例模式的特点: 1.单例类只能有一个对象(实例). 2.单例类必须自己创建自己的唯一实例 . 3.单例类必须给所有其他对象提供这一实例. 设置步骤: 1.将对象的应用成员变量用private来修饰. ...

  2. DBUtil数据库连接单例 —— 简单不简单

    单例大概是我最早产生明确模式意识的设计模式,因为它足够简单粗暴,目的足够明确. 单例么,就是不管怎么访问,都返回一个单一实例就好了,我最早应用在数据库的DBUtil中. public class DB ...

  3. SSH中Action的单例与多例

    Structs2中的Bean默认的是单例,在整个程序运行期间,每个Bean只有一个实例,只要程序在运行,这个实例就一直存在. 对于Action来说,单例就容易出问题.如果客户端每次提交的参数都是一样的 ...

  4. 《连载 | 物联网框架ServerSuperIO教程》- 8.单例通讯模式开发及注意事项

    1.C#跨平台物联网通讯框架ServerSuperIO(SSIO)介绍 <连载 | 物联网框架ServerSuperIO教程>1.4种通讯模式机制. <连载 | 物联网框架Serve ...

  5. static实现单例的隐患

    1. 前言 Java的单例有多种实现方式:单线程下的简单版本.无法在指令重排序下正常工作的Double-Check.static.内部类+static.枚举--.这篇文章要讨论的,是在使用static ...

  6. 架构师养成记--6.单例和多线程、ThreadLocal

    一.ThreadLocal 使用wait/notify方式实现的线程安全,性能将受到很大影响.解决方案是用空间换时间,不用锁也能实现线程安全. 来看一个小例子,在线程内的set.get就是thread ...

  7. 在Swift中实现单例方法

    在写Swift的单例方法之前可以温习一下Objective-C中单例的写法: + (instancetype)sharedSingleton{ static id instance; static d ...

  8. Javascript设计模式学习二(单例)

    定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点 普通的单例模式: 使用一个变量来标记当前是否已经为某个类创建过对象,如果是的话,在下一次获取该类的实例时,直接返回之前创建的对象.比如:使用 ...

  9. OC与Swift单例

    OC: +(instancetype)shareNetworkTools{ static id instance; static dispatch_once_t onceToken; //onceTo ...

  10. 【iOS 单例设计模式】底层解析与运用

    [iOS 单例设计模式]底层解析与运用 一.单例设计名词解释: (官方解释)单例模式确保一个类只有一个实例,自行提供这个实例并向整个系统提供这个实例.(形象比喻)程序 — 公司   单例实例 - 管理 ...

随机推荐

  1. 洛谷【P1854】花店橱窗布置

    https://www.luogu.org/problemnew/show/P1854 题目描述 某花店现有编号由 1 到 F 的 F 束花, 每一束花的品种都不一样. 编号由 1 到 V 的 V 个 ...

  2. 最大公共子串:DP

    标题:最大公共子串 最大公共子串长度问题就是:求两个串的所有子串中能够匹配上的最大长度是多少. 比如:"abcdkkk" 和 "baabcdadabc",可以找 ...

  3. 算法笔记(c++)--使用一个辅助栈排列另一个栈

    算法笔记(c++)--使用一个辅助栈排列另一个栈 仅仅使用一个辅助栈,不使用其他数据结构来排列一个栈,要求,上大下小. 分析下.肯定是先吧主栈中的数据都放到辅助栈中,在辅助栈中上小下大. 1.首先循环 ...

  4. 回归Qt——写在Qt5.10发布之日

    今天偶然看到一条关于Qt5.10发布的消息,发现Qt经历了诺基亚风波之后发展得依然良好,感到很欣慰.回头看上次关注Qt技术还是2011年,那时候用Qt4.7做一个小项目,对于一个写Win32界面和MF ...

  5. PIGCMS 关闭聊天机器人(小黄鸡)

    无脑操作举例 1.找到 WeixinAction.class.php 文件,路径: 你的版本\PigCms\Lib\Action\Home 2.查询 function chat ,在 chat() 函 ...

  6. Bracket Sequences Concatenation Problem括号序列拼接问题(栈+map+思维)

    A bracket(括号) sequence is a string containing only characters "(" and ")".A regu ...

  7. XCode 6.4 Alcatraz 安装的插件不可用

    升级Xcode 6.4后插件都不可用了,解决办法: 1.在 Alcatraz中删除插件并退出Xcode: 2.重新打开Xcode 并安装: 3.退出Xcode: 4.进入Xcode,会提示如图,点击 ...

  8. EF动态排序

    转载的代码,改天再研究 public PageData<T> FindAll(int PageIndex, int PageSize, Expression<Func<T, b ...

  9. MySQL 日志功能详解

    MySQL日志分类 1:查询日志 :query log     2:慢查询日志:slow_query_log 查询执行时长超过指定时长的查询操作所记录日志     3:错误日志:error log   ...

  10. PAT 甲级 1005 Spell It Right

    https://pintia.cn/problem-sets/994805342720868352/problems/994805519074574336 Given a non-negative i ...