单例是个什么鬼

单例是个什么鬼

写在前面

常常听到有人说起单例,那么单例到底是什么呢?又该怎么用呢?或者说,它的应用场景有哪些呢?为了搞清楚这些问题,决定自己亲自实践一下,加深感悟。文中用到的一些单例的实现方式可能是从网上参考的,感谢大家的分享和讲解,这里就不一一引用啦。

单例是什么

单例,顾名思义,就是单个实例,也就是说,某个类如果实现了单例模式,那这个类就只能生成一个实例。单例模式是设计模式的一种,关于设计模式,我大概了解过有工厂模式,抽象工程模式,观察者模式,原型模式等,具体使用哪种设计模式,要视具体应用场景而定。

何时使用单例

在一些应用中,可能会有一些工具类,这些类为其他类服务,本身没有太多数据要保存。如果使用这样的类的时候,每次都用new创建一个对象的话,会增加系统的开销。在实际设计时,这种只需要一个实例对象的类就应该设计为单例模式。

特点

根据单例的实现要求,可以看出单例模式有三个特点:
1、按照单例模式设计的类只能有一个实例
2、这个类的唯一的实例必须是类自身创建的,其他类不能创建
3、这个类创建了唯一的实例后,需要向其他类提供获取唯一实例的方法

如何实现单例

查看相关资料可以发现,单例的实现方式有很多。从单例模式的设计要求可以看出,我们在实现单例的是,要把构造函数设置为私有的。然后需要设计一个共有的返回唯一实例的静态方法。另外,考虑到内存释放的问题,还应该实现析构函数。类的UML如下图:

实现代码如下:
目前这个版本是没有考虑多线程环境的

class Singleton
{
private:
    Singleton()
    {
        test = 7;
    }

    static Singleton *instance;//该类的唯一实例,声明为静态的,属于类所有
    int test;//类的数据成员

public:
    static Singleton *getInstance()//获取类实例的函数
    {
        if ( instance == NULL)//每次检查是否已经创建
        {
            instance = new Singleton();
        }

        return instance;
    }

    static void DestoryInstance()//析构函数
    {
        if (instance != NULL )
        {
            delete instance;
            instance = NULL ;
        }
    }

    int getTest()
    {
        return test;
    }
};

在上述代码中,构造函数设置为私有,然后用共有的getInstance()方法去调用构造函数
在main函数中生成类的实例,代码如下:

int main()
{
    Singleton *p1 = Singleton::getInstance();
    cout<<p1->getTest()<<endl;

    p1->DestoryInstance();
    cout<<"p1 = "<<p1<<endl;
    system("pause");
    return 0;
}

问题:链接错误
原因:静态数据成员未初始化
如果按照上述代码创建实例,在编译时会报如下错误:

一开始看到这个错误的时候,我并没有发现错误出在哪里。好吧,我承认之前看的static关键字的特性都喂狗去了=。=
仔细查看这个错误,发现这个错误发生在链接的时候。在网上搜索这个连接诶错误,看到的大多是模版函数声明和定义的问题,大意是说,如果函数模版的声明放.h文件里面,而函数模版的定义放在.cpp文件里面,在main所在的文件里面使用定义的模版函数,会出现这样的链接错误,原因涉及到编译器的编译原理。。(感觉有点跑偏了,还是不继续解释了。。)反过来看我的代码中出现的错误,从错误提示中推测是某个静态数据成员或者静态成员函数出错了。这时候看看类的声明,里面有一个静态的成员变量,我似乎想起了什么——静态的数据成员一定要初始化啊喂,如果是普通的整型静态变量,没有显示初始化时,编译器会把它初始化为0,但是对于静态数据成员,编译器并不知道要怎么初始化,因此需要我们自己初始化。好的,搞清楚这个问题之后,我们就可以用以下语句初始化了。

//静态数据成员必须在类声明外进行初始化,
//初始化语法为:<数据类型><类名>::<静态数据成员名>=<值>
Singleton* Singleton::instance = new Singleton();//

在初始化的过程中,碰到了一个很有意思的问题,就是在哪里写这句初始化语句。因为看到别人是写在main函数之前的,由于是做样例讲解,类的相关代码和main函数的代码写在了同一个文件里面。这时候我突然觉得很神奇:instance和Singleton()都是Singleton类私有的,为何在类的外面,main函数的前面可以访问?当然,在main函数中是不能访问的,因为私有的数据不能在类外部访问。为了更好的搞清楚这个问题,又创建了一个test.cpp,然后在test.cpp中包含了Singleton.h,再把这句初始化语句剪切到test.cpp中,程序也能通过编译。
test.cpp中的内容如下:

#include"Singleton.h"

Singleton* Singleton::instance = new Singleton();

也就是说,类类型的静态数据成员的初始化可以放在任何一个包含了该类头文件的地方,语法上是没有任何问题的,但在实际使用的时候,为了不破坏封装,我们应该把类的静态数据成员的初始化放在类的时间文件中,或者放在类声明文件的末尾。(类的结束大括号的分号后面)。
好的,下面我们可以创建类的多个实例指针,然后查看这些指针的指向

    Singleton *p1 = Singleton::getInstance();
    Singleton *p2 = Singleton::getInstance();

    cout<<"p1 = "<<p1<<endl;
    cout<<"p2 = "<<p2<<endl;

    cout<<endl;

    p1->DestoryInstance();
    p2->DestoryInstance();

输出结果如下:

这个时候无论我们创建多少指针,最后指向的都是同一个对象

总结

经过大半天的探索,发现单例其实也没那么神秘。我们平时写代码的时候也会编写工具类,只是没有使用单例的习惯,在实现工具类的时候,类内部的方法都设计为静态的,这样这些方法就是为整个类服务的,而不是某个对象。有了单例之后,可以通过单例来访问这些方法,思想其实是相通的。

现在这个版本的单例实现在多线程情况下也会出错,原因是getInstance()方法中的判断语句可能同时被两个线程调用,如果对instance做初始化时不是用构造函数,而是直接用Singleton* Singleton::instance = NULL;初始化,那么两个线程对instance的判断都为空,这时候就会创建两个实例,这不是我们希望的。解决方案是可以引入锁的机制。这些内容就不在本文中记录了。
最后,经过这次的探索,我发现对static数据成员以及单例的认识深刻了很多,感觉要理解C++(当然其他语言也是)的一些机制,还是应该实际操作,然后查看结果,把理论和实践结合起来,才能不断进步。

时间: 2024-08-06 09:53:07

单例是个什么鬼的相关文章

从一个简单的Java单例示例谈谈并发

一个简单的单例示例 单例模式可能是大家经常接触和使用的一个设计模式,你可能会这么写 public class UnsafeLazyInitiallization { private static UnsafeLazyInitiallization instance; private UnsafeLazyInitiallization() { } public static UnsafeLazyInitiallization getInstance(){ if(instance==null){ /

从一个简单的Java单例示例谈谈并发 JMM JUC

原文: http://www.open-open.com/lib/view/open1462871898428.html 一个简单的单例示例 单例模式可能是大家经常接触和使用的一个设计模式,你可能会这么写 public class UnsafeLazyInitiallization { private static UnsafeLazyInitiallization instance; private UnsafeLazyInitiallization() { } public static U

iOS开发——高级篇——iOS中常见的设计模式(MVC/单例/委托/观察者)

关于设计模式这个问题,在网上也找过一些资料,下面是我自己总结的,分享给大家 如果你刚接触设计模式,我们有好消息告诉你!首先,多亏了Cocoa的构建方式,你已经使用了许多的设计模式以及被鼓励的最佳实践. 首先得搞清楚设计模式是个什么鬼,在软件设计领域,设计模式是对通用问题的可复用的解决方案.设计模式是一系列帮你写出更可理解和复用代码的模板,设计模式帮你创建松耦合的代码以便你不需要费多大力就可以改变或者替换代码中的组件 其实iOS中的设计模式有非常多,常用的就下面这四种 一.MVC设计模式(设计模式

一个简单的Java单例示例谈谈并发

一个简单的单例示例 单例模式可能是大家经常接触和使用的一个设计模式,你可能会这么写 public class UnsafeLazyInitiallization { private static UnsafeLazyInitiallization instance; private UnsafeLazyInitiallization() { } public static UnsafeLazyInitiallization getInstance(){ if(instance==null){ /

单例设计模式

一:单例设计模式的定义 单例设计模式,顾名思义,就是在整个程序运行过程中,只向外界提供一个对象,这样做可以避免资源的浪费,例如 我们打开回收站或者ppt时,只会启动一个窗口. 单例模式的java实现: 1:饿汉式 1 /** 2 * 3 */ 4 package com.hlcui.singleton; 5 6 /** 7 * @author Administrator 饿汉式单例类 8 */ 9 public class SingletonDemo { 10 // 1:内部创建一个对象 11

java单例类/

java单例类  一个类只能创建一个实例,那么这个类就是一个单例类 可以重写toString方法 输出想要输出的内容 可以重写equcal来比较想要比较的内容是否相等 对于final修饰的成员变量 一但有了初始值,就不能被重新赋值 static修饰的成员变量可以在静态代码块中 或申明该成员时指定初始值 实例成员可以在非静态代码块中,申明属性,或构造器中指定初始值 final修饰的变量必须要显示初始化 final修饰引用变量不能被重新赋值但是可以改变引用对象的内容分(只要地址值不变) final修

iOS 中的单例设计模式

单例设计模式:在它的核心结构中只包含一个被称为单例类的特殊类.例如文件管理中的NSUserDefault,应用程序中的UIApplication,整个应用程序就这一个单例类,负责应用程序的一些操作,单例在那个文件下都能取得到. 通过单例设计模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节省系统资源.如果希望在系统中某个类的对象只能存在一个,单例模式是最好的选择. 下面来点实在的,创建单例代码上 方法1:基于线程安全创建一个单例 .h做一下声明 + (id)

android里单例对象和某些数据被释放的问题

接触正式的android开发已经有一段时间了,项目的第一个版本终于快完成了.有一次自己在测试的时候,把自己的android项目切到后台,同时打开了几个应用之后重新切回到自己的app,发现报错了.经过排查,发现是自己的单例对象中的数据被释放掉了,也就是int变量的值 变成了0,string变量的值变成了null. 我的单例一开始是这样的(举例); public class UserInfo { private static UserInfo userInfo = null; private int

单例、代理 &amp; 通知

PS:手写单例.代理方法实现 & 通知的简单使用! [ 单例模式,代理设计模式,观察者模式! ] 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. -- GoF “四人帮”<Design Patterns: Elements of Reusable Object-Oriented Software>将设计模式提升到理论高度,并将之规范化.该书提出了23种基本