C++创建型模式-单例模式实现

一、单例模式

1.1 基本概念

单例 Singleton 是设计模式的一种,其特点是只提供唯一一个类的实例,具有全局变量的特点,在任何位置都可以通过接口获取到那个唯一实例;

  • 保证一个类仅有一个实例;
  • 并提供一个访问它的全局访问点;
  • 该实例被所有程序模块所共享。

1.2 应用场景

设备管理器,系统中可能有多个设备,但是只有一个设备管理器,用于管理设备驱动;

 数据池,用来缓存数据的数据结构,需要在一处写,多处读取或者多处写,多处读取;

Windows资源管理器
 

1.3 定义方式

定义一个单例类:

(1)私有化构造函数,以防止外界创建单例类的对象

private Singleton()

不需用拷贝和赋值,在单例模式中,始终只有一个对象 

(2)使用类的私有静态指针,变量指向类的唯一实例。(提供一个自身的静态私有成员变量,以指向类的实例;

private static Singleton * uniqueInstance

(3)使用一个公有的静态方法获取该实例

public static Singleton * getInstance()

1.4 实现方式

懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化。与之对应的是饿汉式单例。(注意,懒汉本身是线程不安全的)

饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。(本身就是线程安全的)

关于如何选择懒汉和饿汉模式:

特点与选择:

  懒汉:在访问量较小时,采用懒汉实现。这是以时间换空间。

  饿汉:由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。

  • 懒汉式 : 指代码运行后,实例并不存在,只有当需要时,才去创建实例(适用于单线程)
  • 饿汉式 : 指代码一运行,实例已经存在,当时需要时,直接去调用即可(适用于多线程)

1.4.1 饿汉式单例模式

指单例实例在程序运行时被立即执行初始化。 

#include <iostream>
#include <process.h>
#include <windows.h>
using namespace std;
 
class Singelton {
private:
    Singelton() {  /*私有的构造函数*/
  
    }
    static Singelton* single;/*私有的静态指针*/
public:
    static Singelton* getSingelton();/*公有的静态方法*/
    static void print();
};
// 饿汉模式的关键:初始化即实例化
Singelton* Singelton::single = new Singelton;

/*公有的静态方法*/
Singelton* Singelton::getSingelton() {
    // 不再需要进行实例化
   // if(single == nullptr){
   //     single = new Singelton;
   // }
    return single;
}

void Singelton::print() {
    cout << "hello singleton" << endl;
}

 
int main(int argc,const char * argv[]) {
   Singelton::getSingelton()->print();     // 构造函数并获得实例,调用静态成员函数
    return 0;
}

补充说明:

(1)静态成员变量

C++ 中,静态成员变量是一种特殊的成员变量。static成员变量不占用对象的内存,而是在对象外开辟内存,即内存分区中的全局数据区分配,程序结束时释放。且静态变量初始化,必须初始化,必须在类声明的外部进行初始化,且不加static关键字。

既可通过对象名访问,也可通过类名访问。

Singelton* Singelton::single = new Singelton;

(2)静态成员函数

  •  静态成员函数中不能使用this指针;
  • 静态成员函数只能访问类的静态成员;

1.4.2 懒汉式单例模式

指单例实例在第一次使用时才进行初始化。

懒汉式(Lazy-Initialization)的方法是直到使用时才实例化对象,也就说直到调用get_instance() 方法的时候才 new 一个单例的对象, 如果不被调用就不会占用内存。

单例模式中的双检锁

#include <iostream>
#include <vector>
using namespace std;

class Singelton {
private:
    Singelton() {  /*私有的构造函数*/

    }
    static Singelton* single;/*私有的静态指针*/
public:
    static Singelton* getSingelton();/*公有的静态方法*/
    static void print();
};
// 饿汉模式的关键:初始化即实例化
Singelton* Singelton::single = nullptr;

/*公有的静态方法*/
Singelton* Singelton::getSingelton() {
    if (single == nullptr) {
        single = new Singelton;
    }
    return single;
}

void Singelton::print() {
    cout << "hello singleton" << endl;
}

 
int main(int argc,const char * argv[]) {
   Singelton::getSingelton()->print();     // 构造函数并获得实例,调用静态成员函数
    return 0;
}

注意: C++11 nullptr和NULL的区别:在编译器解释程序时,NULL会被直接解释成0;nullptr在C++11中就是代表空指针,不能被转换成数字

1.5 单例模式优缺点

优点:
(1)提供了对唯一实例对象的受控访问。
(2)在系统内存中只存在一个对象,因此可以节约系统资源,对于频繁创建和销毁的对象,可以提高系统性能。
缺点:
(1)单例模式中没有抽象层,因此单例类的扩展有困难;
(2)单例类的职责过重,在一定程度上违背了“单一职责原则”。
 

二、单例模式进阶

2.1 模板形式单例模式

模板方法模式:定义一个操作算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

模板的形式: 

#include <iostream>
#include <cassert>
#include <vector>
#include <mutex>
#include <memory>
using namespace std;

template<typename T>
class Singleton {

public:
    //支持0个参数的构造函数
    static T* Instance()
    {
        if (m_pInstance == nullptr) {
            m_pInstance = new T();
        }
        return m_pInstance;
    }
    //支持1个参数的构造函数
    template<typename T0>
    static T* Instance(T0 arg0)
    {
        if (m_pInstance == nullptr) {
            m_pInstance = new T(arg0);
        }
        return m_pInstance;
    }
    //支持2个参数的构造函数
    template<typename T0, typename T1>
    static T* Instance(T0 arg0, T1 arg1)
    {
        if (m_pInstance == nullptr) {
            m_pInstance = new T(arg0, arg1);
        }
        return m_pInstance;
    }
    //获取单例
    static T* GetInstance() {
        static std::mutex m_mutex;
        if (nullptr == m_pInstance) {
            std::lock_guard<std::mutex> lock(m_mutex);
            if (nullptr == m_pInstance) {
                throw std::logic_error("the instance is not init");
            }
        }
        return m_pInstance.get();
    }
    //释放单例
    static void DestroyInstance() {
        delete m_pInstance;
        m_pInstance = nullptr;
    }
private:
    static T* m_pInstance;//私有的静态指针指向唯一的实例
    //static std::shared_ptr<T> m_pInstance;//定义为智能指针不需要手动释放
private:
    //重载操作符即拷贝构造函数设置为private,避免对象间复制和赋值
    Singleton(void);//私有的构造函数
    virtual  ~Singleton(void) {};
    Singleton(const Singleton&);
    Singleton& openrator = (const Singleton&);
};
template<typename T>
T* Singleton<T>::m_pInstance = nullptr;
//测试单例
class A {
public:
    A() {
        cout << "Create A" << endl;
    }
};
class B {
public:
    B(int x) { cout << "Create B" << endl; }
};
class C {
public:
    C(int x, double y) { cout << "Create C" << endl; }
};

int main()
{

    Singleton<A>::Instance();
    //创建B类型的单例
    Singleton<B>::Instance(1);
    //创建C类型的单例
    Singleton<C>::Instance(1, 2.1);

    Singleton<A>::DestroyInstance();
    Singleton<B>::DestroyInstance();
    Singleton<C>::DestroyInstance();
    return 0;
    
}

使用了两个 if判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,避免每次调用 get_instance的方法都加锁,因为锁的开销较大 

 2.2 使用模板单例

 也可以将上述单例类模板写为头文件,当我们需要这个单例类模板时,只需要在自己类里通过friend添加为友元即可,

#include "CSingleton.h"
#pragma once
#ifndef _SINGLETON_H_
#define _SINGLETON_H_
//上述单例模板
#include <iostream>
#include <cassert>
#include <vector>
#include <mutex>
#include <memory>
using namespace std;

template<typename T>
class Singleton {

public:
    //支持0个参数的构造函数
    static T* Instance()
    {
        if (m_pInstance == nullptr) {
            m_pInstance = new T();
        }
        return m_pInstance;
    }
    //支持1个参数的构造函数
    template<typename T0>
    static T* Instance(T0 arg0)
    {
        if (m_pInstance == nullptr) {
            m_pInstance = new T(arg0);
        }
        return m_pInstance;
    }
    //支持2个参数的构造函数
    template<typename T0, typename T1>
    static T* Instance(T0 arg0, T1 arg1)
    {
        if (m_pInstance == nullptr) {
            m_pInstance = new T(arg0, arg1);
        }
        return m_pInstance;
    }
    //获取单例
    static T* GetInstance() {
        static std::mutex m_mutex;
        if (nullptr == m_pInstance) {
            std::lock_guard<std::mutex> lock(m_mutex);
            if (nullptr == m_pInstance) {
                throw std::logic_error("the instance is not init");
            }
        }
        return m_pInstance.get();
    }
    //释放单例
    static void DestroyInstance() {
        delete m_pInstance;
        m_pInstance = nullptr;
    }
private:
    static T* m_pInstance;//私有的静态指针指向唯一的实例
    //static std::shared_ptr<T> m_pInstance;//定义为智能指针不需要手动释放
private:
    //重载操作符即拷贝构造函数设置为private,避免对象间复制和赋值
    Singleton(void);//私有的构造函数
    virtual  ~Singleton(void) {};
    Singleton(const Singleton&);
    Singleton& openrator = (const Singleton&);
};
template<typename T>
T* Singleton<T>::m_pInstance = nullptr;
#endif

使用方法

// CMakeProject1.cpp: 定义应用程序的入口点。
//

#include "CMakeProject1.h"
#include "CSingleton.h"

using namespace std;
class Test
{
    friend class Singleton<Test>;  //声明Test的友元为单例类模板
private:
    Test()
    {
    }

    Test& operator = (const Test& t);
    Test(const Test&);

public:
    void print()
    {
        cout << "this my test singleton "<< endl;
    }
};

int main()
{
    Test* pt1 = Singleton<Test>::Instance();
    pt1->print();
    Singleton<Test>::DestroyInstance();
	cout << "Hello CMake." << endl;
	return 0;
}

 

 2.3  单例初始化确保安全

单例类的初始化,考虑到多线程安全,需要进行加锁控制。C++11中提供的call_once可以很好的满足这种需求。

std::call_once使用方法

#include<mutex>
        template <class Fn, class... Args>
        void call_once (once_flag& flag, Fn&& fn, Args&&...args);

  第一个参数是std::once_flag的对象(once_flag是不允许修改的,其拷贝构造函数和operator=函数都声明为delete),第二个参数可调用实体,即要求只执行一次的代码,后面可变参数是其参数列表。

call_once保证函数fn只被执行一次,如果有多个线程同时执行函数fn调用,则只有一个活动线程(active call)会执行函数,其他的线程在这个线程执行返回之前会处于”passive execution”(被动执行状态)——不会直接返回,直到活动线程对fn调用结束才返回。

#include <mutex>
#include <thread>
#include <memory>
template<typename T>
class Singleton {
 public:
    static T* GetInstance() {
        if (nullptr == m_instance_) {
            std::call_once(m_flag_, []{m_instance_=std::make_shared<T>();});
        }
        return m_instance_.get();
    }


 private:
    static std::once_flag m_flag_;
    static std::shared_ptr<T> m_instance_;
};

template<typename T>
std::shared_ptr<T> Singleton<T>::m_instance_ = nullptr;

template<typename T>
std::once_flag Singleton<T>::m_flag_;

扩展: 

 C++11 中,可在 "禁止使用" 的特殊成员函数声明后加 "= delete",而需要保留的加 "= default" 或者不采取操作 

#include <iostream>
#include <cassert>
#include <vector>
using namespace std;

template <typename T>
class Singleton {
public:
    static T& Instance();
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() = default;
    ~Singleton() = default;
};

template <typename T>
T& Singleton<T>::Instance() {
    static T instance;
    return instance;
}
class A {};

int main(){
    auto& a = Singleton<A>::Instance();
        auto& b = Singleton<A>::Instance();
        assert(&a == &b);
    }

2.4 优化的单例模式-使用可变参数模板

使用可变参数模板

#include <iostream>
using namespace std;
template <typename T>
class Singleton
{
public:
	template<typename... Args>
	static T* Instance(Args&&... args)
	{
		if (m_pInstance == nullptr)
		{
			m_pInstance = new T(std::forward<Args>(args)...);//完美转发
		}
		return m_pInstance;
	}
//获取单例
	static T* GetInstance()
	{
		if (m_pInstance == nullptr)
		{
			throw std::logic_error("the instance is not init,please initialize the instance first");
		}
	
		return m_pInstance;
	}
//释放单例
	static void DestoryInstance()
	{
		delete m_pInstance;
		m_pInstance = nullptr;
	}
private:
	//不允许复制和赋值
	Singleton(void);
	virtual ~ Singleton(void);
	Singleton(const Singleton&);
	Singleton& operator = (const Singleton&);
private:
	static T* m_pInstance;//私有静态指针
};
template <class T> T*	Singleton<T>::m_pInstance = nullptr;
struct A
{
	A(const string&) { cout << "lvaue" << endl; }
	A(string&& x) { cout << "rvalue" << endl; }

};

struct B
{
	B(const string&) { cout << "lvaue" << endl; }
	B(string&& x) { cout << "rvalue" << endl; }
};

struct C
{
	C(int x, double y) {}
	void Fun() { cout << "test" << endl; }
};


int main()
{
	string str = "bb";
	//创建A类型的单例
	Singleton<A>::Instance(str);	
	//创建B类型的单例,临时变量str被move之后变成右值
	Singleton<B>::Instance(std::move(str));

//创建C类型的单例
	Singleton<C>::Instance(1, 3.14);
	//获取单例并调用单例对象方法
	Singleton<C>::GetInstance()->Fun();

//释放单例
	Singleton<A>::DestoryInstance();
	Singleton<B>::DestoryInstance();
	Singleton<C>::DestoryInstance();
	std::cout << "Hello World!\n";
}

运行结果:

lvalue

rvalue

test

Hello World 

3 问题:为什么单例模式的类模板要声明为这个类的友元类?

使用 上述 2.2 案例进行测试

(1)类Test中去掉 友元

class Test
{
	//class Singleton<Test>;  //例类模板不声明为友元
private:
	Test()
	{
	}

	Test& operator = (const Test& t);
	Test(const Test&);

public:
	void print()
	{
		cout << "this my test singleton,test pubic " << endl;
	}
};

重新编译:编译失败

/home/hanyangyang/my_test/test_singleton/main.cpp:9:2: 错误:‘Test::Test()’是私有的
  Test()
  ^
In file included from /home/hanyangyang/my_test/test_singleton/main.cpp:2:0:
/home/hanyangyang/my_test/test_singleton/CSingleton.h:20:16: 错误:在此上下文中
  m_pInstance = new T();
 

(2)声明为友元类,访问Test类的私有成员和保护成员

class Test
{
	friend class Singleton<Test>;  //声明Test的友元为单例类模板
private:
	Test()
	{
	}

	Test& operator = (const Test& t);
	Test(const Test&);

public:
	void print()
	{
		cout << "this my test singleton,test pubic " << endl;
	}
protected:
	void print1()
	{
		cout << "this my test singleton,test protected " << endl;
	}
private:
	void print2()
	{
		cout << "this my test singleton,test private " << endl;
	} 
};

int main()
{
	Test* pt1 = Singleton<Test>::Instance();
	pt1->print();
	pt1->print1();
	pt1->print2(); */
	Singleton<Test>::DestroyInstance();
	cout << "Hello CMake." << endl;
	return 0;
}

 编译失败:

/home/hanyangyang/my_test/test_singleton/main.cpp: 在函数‘int main()’中:
/home/hanyangyang/my_test/test_singleton/main.cpp:22:7: 错误:‘void Test::print1()’是保护的
  void print1()
       ^
/home/hanyangyang/my_test/test_singleton/main.cpp:37:14: 错误:在此上下文中

 pt1->print1();
              ^
/home/hanyangyang/my_test/test_singleton/main.cpp:27:7: 错误:‘void Test::print2()’是私有的
  void print2()
       ^
/home/hanyangyang/my_test/test_singleton/main.cpp:38:14: 错误:在此上下文中
  pt1->print2(); */
              ^
/home/hanyangyang/my_test/test_singleton/main.cpp:38:18: 错误:expected primary-expression before ‘/’ token
  pt1->print2(); */
(3)

参考文献:

 【1】C++ 单例模式总结与剖析:C++ 单例模式总结与剖析 - 行者孙 - 博客园

【2】c++11设计模式 单例模式 线程安全 泛型单例:c++11设计模式 单例模式 线程安全 泛型单例_daxiang10m的博客-CSDN博客

【3】C++11中的std::call_once:C++11中的std::call_once_爱很遥远-CSDN博客_c++ call_once

【4】 C++ 单例模式(懒汉、饿汉模式)

【5】C++ 单例模式(懒汉、饿汉模式) - 我得去图书馆了 - 博客园

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值