栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

设计模式

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

设计模式

文章目录
  • 单例模式介绍
  • 实现要点
  • 单例模式的写法(C++)
    • 饿汉式(Eager initialization)
      • 优缺点
      • 普遍方式
      • shared_ptr与自定义删除器方式
    • 懒汉式(Lazy-Initialization)
      • 优缺点
      • 线程安全、内存安全的懒汉式单例 (智能指针,锁)
      • Magic Static式单例(局部静态变量)
  • 单例类模版(C++)
    • CRTP 奇异递归模板模式实现
      • 基类模板的实现要点
      • 代码示例
    • 不需要在子类声明友元的实现方法
      • 实现要点
      • 代码示例
  • 总结


单例模式介绍

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


常见的运用场景

  • 设备管理器,系统中可能有多个设备,但是只有一个设备管理器,用于管理设备驱动;
  • 数据池,用来缓存数据的数据结构,需要在一处写,多处读取或者多处写,多处读取;
  • 回收站,在整个系统运行过程中,回收站一直维护着仅有的一个实例。
  • 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。’
  • 网站的计数器,一般也是采用单例模式实现,否则难以同步。
实现要点
  • 全局只有一个实例:static 特性,同时禁止用户自己声明并定义实例(把构造函数设为 private)
  • 线程安全
  • 禁止赋值和拷贝
  • 用户通过接口获取实例:使用 static 类成员函数
单例模式的写法(C++)

单例模式的写法分为两种,区别在于实例化的时机.

  • 饿汉式(Eager initialization)

像一个饿汉一样,不管需不需要用到实例都要去创建实例,即在类产生的时候就创建好实例,这是一种空间换时间的做法.作为一个饿汉而言,体现了它的本质——“我全都要”.


  • 优缺点

优点:

  1. 避免了线程同步问题.
  2. 线程安全在类加载的同时已经创建好一个静态对象,调用时反应速度快.

缺点:

  1. 来类装载的时候就完成了实例化,没有达到Lazy Loading的效果.
  2. 如果从始至终未使用过这个实例,则会造成实例的浪费.

  • 普遍方式

代码示例:

#pragma once

#include 

class Singleton {
 //构造器私有化
private:
	 Singleton() {}
	 Singleton(const Singleton&)
	 Singleton& operator=(const Singleton&);
//想要这个实例在程序运行的整个过程中都存在,所以我们不允许实例自己主动调用析构函数释放对象.
    ~Singleton(){) 
    
	 static Singleton instance;     //方式一:类对象
  	 static Singleton* instance =new Singleton(); //方式二:类对象的指针
//提供一个公有的静态方法,返回实例对象.
public :
	 static Singleton getInstance()  { return &instance; } //方式一:返回类对象
	 static Singleton* Singleton::getInstance(){ return instance; }//方式二:返回类对象指针
	 static void deleteInstance()	 { delete instance; } //方式二:用来销毁对象的指针的实例
 };

  • shared_ptr与自定义删除器方式

代码示例:

#pragma once

#include 
#include 
using namespace std;
class Singleton{
private:
    Singleton(){
        cout <<__TIME__<<":" << __FUNCTION__<< endl;
    }
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
    ~Singleton(){
        cout <<__TIME__<<":" <<__FUNCTION__ << endl;
    }
    static void DestroyInstance(Singleton*);    //自定义一个释放实例的函数
    static shared_ptr instance;  //这是我们的单例对象,它是一个类对象的指> >     
public:
    static shared_ptr getInstance();
    shared_ptr Singleton::instance(new Singleton(),Singleton::DestroyInstance); 
    shared_ptr Singleton::getInstance(){ return instance;  }
    void Singleton::DestroyInstance(Singleton*){ cout <<__TIME__<<":" << __FUNCTION__<< endl;}
  };
  • 懒汉式(Lazy-Initialization)

像一个懒汉一样,需要用到创建实例了程序再去创建实例,不需要创建实例程序就“懒得”去创建实例,这是一种时间换空间的做法.
懒汉式的方法是直到使用时才实例化对象,也就说直到调用get_instance() 方法的时候才 new 一个单例的对象, 如果不被调用就不会占用内存。


  • 优缺点

优点:
单例对象的生成是在用户需要使用的时候再去构造,提高了应用的启动速度.
缺点:
需要为保证线程安全增加额外的开销.


  • 线程安全、内存安全的懒汉式单例 (智能指针,锁)

优点:
1.基于 shared_ptr, 用了C++比较倡导的 RAII思想,用对象管理资源,当 shared_ptr 析构的时候,new 出来的对象也会被 delete掉,以此避免内存泄漏.
2.加了锁,使用互斥量来达到线程安全。使用两个 if判断语句的技术称为双检锁,好处是,只有判断指针为空的时候才加锁,避免每次调用 get_instance的方法都加锁,锁的开销毕竟还是有点大的.
缺点:
1.使用智能指针会要求用户也得使用智能指针,非必要不应该提出这种约束.
2.使用锁也有开销; 同时代码量也增多了,实现上我们希望越简单越好.
3.在某些平台(与编译器和指令集架构有关),双检锁会失效.


代码示例:

#pragma once

#include 
#include  // shared_ptr
#include   // mutex

class Singleton{
public:
	typedef std::shared_ptr Ptr;
 	Singleton(Singleton&)=delete;
 	Singleton& operator=(const Singleton&)=delete;
	 ~Singleton(){	std::cout<<"destructor called!"<
        // "double checked lock"
        if(m_instance_ptr==nullptr)
        {
            std::lock_guard lk(m_mutex);
            if(m_instance_ptr == nullptr)
            {
              m_instance_ptr = std::shared_ptr(new Singleton);
            }
        }
        return m_instance_ptr;
    }
private:
    Singleton() {std::cout<<"constructor called!"< 
 
  • Magic Static式单例(局部静态变量)

这种方法又叫做 Meyers’ SingletonMeyer’s的单例, 是著名的写出《Effective C++》系列书籍的作者 Meyers 提出的。所用到的特性是在C++11标准中的Magic Static特性:

If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。
这样保证了并发线程在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性。
C++静态变量的生存期 是从声明到程序结束,这也是一种懒汉式.


优点:
1.通过局部静态变量的特性保证了线程安全 (C++11, GCC > 4.3, VS2015支持该特性);
2.不需要使用共享指针,代码简洁;
3.注意在使用的时候需要声明单例的引用 Single& 才能获取对象。


代码示例:

#pragma once

#include 
using namespace std;

class Singleton{
public:
	~Singleton(){cout <<__TIME__<<":"<< __FUNCTION__<< endl;}

	Singleton(const Singleton&)=delete;
	Singleton& operator=(const Singleton&)=delete;

	static Singleton& get_instance()
	{
		static Singleton instance;
		return instance;
	}
private:
Singleton(){cout <<__TIME__<<":" <<__FUNCTION__<< endl;}
};
单例类模版(C++)
  • CRTP 奇异递归模板模式实现
基类模板的实现要点

1.构造函数需要是 protected,这样子类才能继承.
2.使用了奇异递归模板模式CRTP(Curiously recurring template pattern).
3.get instance 方法和 static local方法一个原理.
4.在这里基类的析构函数可以不需要 virtual ,因为子类在应用中只会用 Derived 类型,保证了析构时和构造时的类型一致.


  • 代码示例
#pragma once
// brief: a singleton base class offering an easy way to create singleton
#include 

template class Singleton{ 
public:
    static T& get_instance()
    {
        static T instance;
        return instance;
    }
    virtual ~Singleton()
    {
        std::cout<<"destructor called!"<
        std::cout<<"constructor called!"<
{   
 // !!!!attention!!!    
 // needs to be friend in order to   
 // access the private constructor/destructor    
		friend class Singleton; 
public:    
		DerivedSingle(const DerivedSingle&)=delete;    
		DerivedSingle& operator =(const DerivedSingle&)= delete; 
private:    
		DerivedSingle()=default;
 };

int main(int argc, char* argv[])
{
    DerivedSingle& instance1 = DerivedSingle::get_instance();
    DerivedSingle& instance2 = DerivedSingle::get_instance();
    return 0; 
}

  • 不需要在子类声明友元的实现方法
  • 实现要点

精髓在于使用一个代理类 token,子类构造函数需要传递token类才能构造,把 token保护其起来, 然后子类的构造函数就可以是公有的了,这个子类只有Derived(token)的这样的构造函数,这样用户就无法自己定义一个类的实例了,起到控制其唯一性的作用。


  • 代码示例
#pragma once
 // brief: a singleton base class offering an easy way to create singleton
#include 

template class Singleton{ 
public:
    static T& get_instance() noexcept(std::is_nothrow_constructible::value)
    {
        static T instance{token()};
        return instance;
    }
    virtual ~Singleton() =default;
    Singleton(const Singleton&)=delete;
    Singleton& operator =(const Singleton&)=delete; 
protected:
    struct token{}; // helper class
    Singleton() noexcept=default; 
};


 
// Example:
// constructor should be public because protected `token` control theccess


class DerivedSingle:public Singleton{ 
public:   
	   DerivedSingle(token){ std::cout<<"destructor called!"<std::cout<<"constructor called!"<
    DerivedSingle& instance1 = DerivedSingle::get_instance();
    DerivedSingle& instance2 = DerivedSingle::get_instance();
    return 0; 
} 
总结

当你需要系统中只有唯一一个实例存在的类的全局变量的时候才使用单例.
使用时,越小越好,越简单越好,保证线程安全,内存不泄露.

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/982302.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号