【设计模式】C++单例模式的几种写法——Java自动加载内部类对象,C++怎么破?
单例模式是最简单的设计模式,就让我像玩简单的游戏一样写下去吧。
v1: 简单模式
和这个版本有过一面之缘,但不敢苟同。
class Singleton
{
private:
Singleton() {}
public:
static Singleton * getIns()
{
static Singleton * ins = new Singleton();
return ins;
}
};
问题:何时析构不明确;最重要的是调用多次getIns函数会产生多个static Singleton指针,指向每次都调用都new出来的实例。
v2: 一般模式
典型写法
class Singleton
{
private:
Singleton() {}
static Singleton * ins;
public:
static Singleton * getIns()
{
if(!ins) ins = new Singleton();
return ins;
}
}; static Singleton * Singleton::ins = NULL;
问题:仍然未考虑析构问题;对象可能被复制出多个副本。
Java中由于允许在调用构造函数之前先初始化变量,因此有这样一种写法:
public class Singleton
{
private Singleton() {}
public static Singleton ins = new Singleton();
public static Singleton * getIns()
{
return ins;
}
};
简洁明了,也是蛮OK啦,析构也省了,并且由于初始化这个语句是JVM做的,因此人工的同步也省了(不带这么欺负C++程序员的 = =)。
v3: 加强模式
加入私有的复制构造函数以防出现单例对象的副本;加入一个内部静态类,整个程序结束后,静态类随着其他静态变量消亡,此时调用析构函数将ins析构。
class Singleton
{
private:
Singleton() {}
Singleton(const Singleton & s) {}
Singleton & operator = (const Singleton & s) {} static Singleton * ins; public:
static Singleton * getIns()
{
if(!ins) ins = new Singleton();
return ins;
} class CGarbo // 内部类
{
public:
~CGarbo()
{
if(Singleton::ins)
delete Singleton::ins;
}
};
static CGarbo Garbo;
};
static Singleton * Singleton::ins = NULL;
问题:不是线程安全的。
v4: hard模式
static Singleton * getIns()
{
pthread_mutex_lock( &mutex_lock );
if(!ins) ins = new Singleton();
pthread_mutex_unlock( &mutex_lock );
return ins;
}
问题:不管ins是不是空,都要加锁,代价高
v5: 再接再励模式
当ins不为空时,反正getIns函数不管读写,直接返回就好了,读写锁尽可交给使用者。
static Singleton * getIns()
{
if(!ins)
{
pthread_mutex_lock( &mutex_lock );
if(!ins) ins = new Singleton();
pthread_mutex_unlock( &mutex_lock );
}
return ins;
}
自然,getIns加了锁,析构的时候也同样要加锁。继续学习,再接再励!
v6:虎牢关模式
实际上,只有在第一次创建单例的时候需要获得锁。既然可以用一个内部类的静态实例实现单例对象的自动释放,何不把该静态对象的初始化也交由该内部类来维护呢:
class Singleton
{
public:
Singleton() {}
Singleton(const Singleton &s) {}
Singleton & operator = (const Singleton & s) {} public:
static Singleton *getInstance()
{
return GC::pinstance;
} class GC
{
public:
static Singleton *pinstance;
GC()
{
pinstance = new Singleton();
}
~GC()
{
if( pinstance )
{
delete pinstance;
}
}
};
GC gc;
};
Singleton * Singleton::GC::pinstance = nullptr;
注意,此处将Singleton的构造函数改成了public。因为在系统中没有Singleton实例时,Singleton的static成员也是不会被创建的,因此需将构造函数改成public,在使用词单例类时,先声明一个Singleton对象。
如此,进程启动,在初始化阶段先初始化了不属于任何实例对象的Singleton的各个静态成员,顺便new好了我们所需的单例对象指针,这一过程不是线程来干的,因此这样的new操作是安全的。而在程序运行结束时,gc自动析构,将单例对象释放掉,这一过程也不是线程来干的,因此也是线程安全的,そして,至于pthread_mutex? WTF。而由于不管存在多少Singleton对象,每次getInstance必然获取到的是同一个instance。
v7: 终极模式
等等,所有“非你莫属”的类,都要改写成单例模式么?NONONO,能不能只写一个单例模式,所有需要限制实例个数的类都可复用之?这样,当系统中需要多个不同类的单例对象时,可以节约许多代码。当然可以做到,用模板就可以:
template <typename T>
class Singleton
{
public:
Singleton() {}
Singleton(const Singleton &s) {}
Singleton & operator = (const Singleton & s) {} public:
static Singleton *getInstance()
{
return GC::pinstance;
} class GC
{
public:
static T *pinstance;
GC()
{
pinstance = new T();
}
~GC()
{
if( pinstance )
{
delete pinstance; pinstance = nullptr;
}
}
};
GC gc;
};
template <typename T>
T * Singleton<T>::GC::pinstance = nullptr;
当然很显然,使用了模板的话,就需要一些约定俗成的东西了,例如在实例化的时候需要一个不带参数的构造函数T()。在使用时,对于用Singleton<T>::getInstance函数返回得到的对象指针,就是非你莫属的同一个指针了:
int main()
{
Singleton<int> sgton;
int *p1 = Singleton<int>::getInstance();
int *p2 = Singleton<int>::getInstance();
int *p3 = Singleton<int>::getInstance();
printf("%x\n", p1);
cout <<(p1==p2)<<endl;
cout <<(p2==p3)<<endl;
return ;
}
以上测试代码的结果为0x........ 1 1.
ちょっと待って!!还不够!以上代码还是有问题的!注意此时我们要限制对象个数的不是Singleton对象了,而是T对象。因此设计目标是:不管申明多少个Singleton对象都是合法的,而不管申明了多少个Singleton对象,调用getInstance都只会得到一个T对象。
再看上面的代码,如果用户手贱声明了多个Singleton对象会怎么样呢?对于一个Singleton类的static对象,只要程序中的Singleton对象个数大于0,就会维护唯一的一个static变量。可是上面的static变量是个指针!!这就意味着每次构造Singleton对象时,都会调用GC构造函数中的new运算符,将一个新的T对象的地址赋值给pinstance!!那旧的那个呢?WHO CARES!!这样一来轻则导致内存泄漏,重则导致系统运行出错——如果先申明了一个Singleton对象,用着用着中间又声明出来一个Singleton对象,那么pinstance指向的内容就被偷换了!
说了这么多,为了防止发生这样的事情,只需要在new的时候和delete的时候稍稍注意一下即可,为了防止new两次,加上if语句;为了防止delete两次(当然在其他场合也一样),养成delete之后就将指针赋为空,以免指针在其他地方被误引用。
template <typename T>
class Singleton
{
public:
Singleton() {}
Singleton(const Singleton &s) {}
Singleton & operator = (const Singleton & s) {} public:
static T *getInstance()
{
return GC::pinstance;
} class GC
{
public:
static T *pinstance;
GC()
{
if( !pinstance ) pinstance = new T();
}
~GC()
{
if( pinstance )
{
delete pinstance; pinstance = nullptr;
}
}
};
static GC gc;
};
template <typename T>
T * Singleton<T>::GC::pinstance = nullptr;
这样一来,只要在起子线程之前声明一个Singleton对象,那么不管后面是main线程还是子线程声明了其他的Singleton对象,T对象都不会出现第二个。
反思:
1 为什么Java用内部类维护单例?Java这么做是为了在不写synchronized关键字(减少锁的代价)的情况下,采用延时加载的方式实现单例。怎么做到不用锁而做到线程安全呢?在Java中,内部类都只会加载一次,因此内部类的初始化过程有且仅有一次,而这个过程是JVM干的,不受用户多线程的影响。为什么是延时加载呢?只有getInstance()用到了内部类的时候,才会加载内部类。
2 为什么C++写法和Java延时加载且线程安全的单例写法不一样呢?C++是不会加载内部类的,只有当有对象需要实例化时,才调用其构造函数进行初始化,同时也初始化内部类的static变量。如果像Java一样只是定义一个内部类而不去实例化它,是不行的。因此按照上面的写法,使用该单例模板类必须先定义一个Singleton对象,这时候由于内部类作为Singleton的成员,会先被构造,“顺便地”就正确地new出了一个T对象,pinstance指针是static变量,一个类最多只有一个,所以不会有两个pinstance,那么只要再内部类的构造中防止new出两个T对象,就可以保证进程中有唯一的pinstance指向唯一的T。
to be continue
【设计模式】C++单例模式的几种写法——Java自动加载内部类对象,C++怎么破?的更多相关文章
- 设计模式之单例模式的几种写法——java
对于设计模式的使用场景和好处,之前有介绍一篇,今天主要是单例模式的编写方式,直接看代码吧 单例模式之饿汉模式,不会懒加载.线程安全 /** * @Author wangtao * @Descripti ...
- Java设计模式之单例模式(七种写法)
Java设计模式之单例模式(七种写法) 第一种,懒汉式,lazy初始化,线程不安全,多线程中无法工作: public class Singleton { private static Singleto ...
- java设计模式之单例模式(几种写法及比较)
概念: Java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建 ...
- Android设计模式之单例模式的七种写法
一 单例模式介绍及它的使用场景 单例模式是应用最广的模式,也是我最先知道的一种设计模式.在深入了解单例模式之前.每当遇到如:getInstance()这样的创建实例的代码时,我都会把它当做一种单例模式 ...
- jquery文档加载几种写法,图片加载写法
jquery文档加载写法: $(function(){ }) ; //个人最常使用方式 $(document).ready(function(){ }); //调用文档对象下的ready方法传入一个函 ...
- java单例模式的几种写法比较
概念: Java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建 ...
- Java 单例模式的七种写法
Java 单例模式的七种写法 第一种(懒汉,线程不安全) public class Singleton { private static Singleton instance; private Sin ...
- Java单例模式的6种写法
在Java中,单例有很多种写法,面试时,手写代码环节,除了写算法题,有时候也会让手写单例模式,这里记录一下单例的几种写法和优缺点. 初级写法 懒汉式 饿汉式 双锁检验 内部类 枚举式 1.初级写法 p ...
- 单例模式:Java单例模式的几种写法及它们的优缺点
总结下Java单例模式的几种写法: 1. 饿汉式 public class Singleton { private static Singleton instance = new Singleton( ...
随机推荐
- event.srcElement就是指向触发事件的元素,他是什么就有什么的属性
原文发布时间为:2009-06-29 -- 来源于本人的百度文章 [由搬家工具导入] 得到或设置触发事件的对象。 event.srcElement就是指向触发事件的元素,他是什么就有什么的属性 s ...
- hdu 3518 Boring counting 后缀数组 height分组
题目链接 题意 对于给定的字符串,求有多少个 不重叠的子串 出现次数 \(\geq 2\). 思路 枚举子串长度 \(len\),以此作为分界值来对 \(height\) 值进行划分. 显然,对于每一 ...
- bzoj 1228 [SDOI2009]E&D SG函数打表 找规律
题目链接 Description 桌子上有2n 堆石子,编号为1..2n.将第2k-1 堆与第2k 堆(1 ≤ k ≤ n)为同一组.第i堆的石子个数用一个正整数Si表示.一次分割操作指的是,从桌子上 ...
- C#反射(Reflection)详解
1. 什么是反射2. 命名空间与装配件的关系3. 运行期得到类型信息有什么用4. 如何使用反射获取类型5. 如何根据类型来动态创建对象6. 如何获取方法以及动态调用方法7. 动态创建委托 1.什么是反 ...
- 四、Ubuntu 一些常用命令
1.锁定root用户 :sudo passwd -l root 2.解锁root用户 :sudo passwd -u root 3.切换身份:su root 或者 su 其他用户名,然后输入密码, ...
- 最短Hamilton路径
题目描述 给定一张 n(n≤20) 个点的带权无向图,点从 0~n-1 标号,求起点 0 到终点 n-1 的最短Hamilton路径. Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每 ...
- 维生素d
作者:卓正内科李爽 链接:https://www.guokr.com/article/440438/来源:果壳本文版权属于果壳网(guokr.com),禁止转载.如有需要,请联系sns@guokr.c ...
- hdu 3062 Party 2-SAT
题目链接:HDU - 3062 有n对夫妻被邀请参加一个聚会,因为场地的问题,每对夫妻中只有1人可以列席.在2n 个人中,某些人之间有着很大的矛盾(当然夫妻之间是没有矛盾的),有矛盾的2个人是不会同时 ...
- 修改Tomcat服务中的端口配置
1.修改Tomcat服务中的端口配置: 分别修改安装目录下的conf子目录中的server.xml文件(注意:两个文件中对应的端口号要不一样),修改如下 : a. 修改Shutdown端口(默认为80 ...
- linux 学习解决归档管理器打开rar和zip中文文件名乱码问题
在ubunut下打开windows下压缩的rar文件和zip压缩文件出现中文文件名乱码的问题真的很头疼.文件名乱码其实也没有什么关系是不?至少重命名再改回来或者是使用英文命名都可以克服.不巧的是,如此 ...