1、单例模式
单例模式是C++的一种设计模式,其核心思想是确保一个类中只有一个实例,也就是说一个类中仅有一个对象,且能被其他变量访问。相当于创建了一个全局变量。
2、使用场景
(1)资源共享:例如log/配置文件等在整个程序中都需要使用,使用单例模式可以确保这些资源都仅有一个实例,避免资源浪费。
(2)数据:用来缓存数据的数据结构,需要在一处写,多处读取/多处写,多处读取。
(3)单元测试:可以用来创建单元类,搭建单元测试框架。
3、定义
(1)私有化构造函数:防止外界创建单例类的对象,不需要赋值。
(2)静态成员变量:提供一个自身的静态私有成员变量,以指向类的实例。
(3)静态方法:提供一个公有的静态成员函数,用来返回这个唯一实例。
4、实现
(1)模式一:“懒汉模式”
<1>特点: 在第一次调用getInstance()
方法时才创建单例对象。只适用于单线程,如果多个线程同时调用getInstance(),可能会同时创建多个单例对象,从而不安全。因此多线程中需要注意线程安全,最好加锁防止冲突。
<2>代码示例:
#include <iostream>
#include <unistd.h>
class Singleton
{
private:
Singleton() // 构造函数与析构函数的私有化
{
std::cout << "懒汉模式" << "\n";
}
~Singleton() {}
Singleton(const Singleton &) = delete; // 禁止拷贝
Singleton &operator=(const Singleton &) = delete; // 禁止赋值
static Singleton *data; // 使用Singleton*作为访问唯一对象的入口
public:
static Singleton *GetInstance() // 公有的静态方法,外部只能通过该函数获取data
{
if (data == nullptr)
{
if (data == nullptr)
data = new Singleton(); // 需要时才创建实例
}
return data;
}
};
Singleton *Singleton::data = nullptr; // data的初始化
int main()
{
// 只有调用GetInstance时,才会开辟空间
std::cout << Singleton::GetInstance() << "\n";
return 0;
}
运行结果:构造函数中的打印和data的地址
(2)模式二:“饿汉模式”
<1>特点: 在类加载时就立即创建单例对象,并将其存储在静态成员变量中。这种模式相比较“懒汉模式”而言,更适合多线程,由于一开始就创建好对象,所以在多线程环境中不会出现多个线程同时创建对象的情况。但可能会出现资源浪费的情形。
<2>代码示例:
#include <iostream>
#include <unistd.h>
class Singleton
{
private:
Singleton() // 构造函数与析构函数的私有化
{
std::cout << "饿汉模式" << "\n";
}
~Singleton() {}
Singleton(const Singleton &) = delete; // 禁止拷贝
Singleton &operator=(const Singleton &) = delete; //禁止赋值
static Singleton data; // 静态实例,程序启动时创建
public:
static Singleton *GetInstance() //公有的静态方法,外部只能通过该函数获取data
{
return &data;
}
};
Singleton Singleton::data; // data的初始化,必须在类外。
int main()
{
// 以下两种实例化方式都无法实例化出对象
// Singleton s;
// Singleton s1(s);
std::cout << Singleton::GetInstance() << "\n";
return 0;
}
运行结果:构造函数中的打印和data的地址
5、进阶使用
(1)融入模板的单例模式
代码示例:
#include <iostream>
using namespace std;
template <typename T>
class Singleton
{
public:
static T &Getinstance() // 外界可访问的唯一接口
{
Init();
return *instance;
}
private:
static void Init()
{
if (instance == 0)
{
instance = new T; // 初始化实例
atexit(DestroyInstance); // 当程序结束时析构
}
}
static void DestroyInstance()
{
delete instance;
instance = nullptr;
}
Singleton();
~Singleton();
Singleton(const Singleton<T> &) {}
Singleton<T> &operator=(const Singleton<T> &) {}
static T *instance; // 私有的静态指针,指向唯一的实例,也可以用智能指针,定义为智能指针时不需要手动释放 static std::shared_ptr<T>...
};
template <typename T>
T *Singleton<T>::instance = nullptr; // 初始化
// example
class A
{
public:
A()
{
std::cout << "begin...." << "\n";
}
~A()
{
std::cout << "end...." << "\n";
}
void Run()
{
std::cout << "run...." << "\n";
}
};
typedef Singleton<A> a;
int main()
{
a::Getinstance().Run();
// Singleton<A>::Getinstance().Run(); //两种方法均可,
return 0;
}
使用的时候最好在初始化实例的时候即 instance = new T 的前后加上锁,防止冲突。
(2)使用模板单例,添加友元函数
日常的使用习惯会将上述单例类模板写为头文件,当我们需要这个单例类模板时,只需要在自己类里通过friend添加为友元即可。下方头文件不再赘述,具体描述应用程序的编写
代码示例:
#include"Singleton.h"
using namespace std;
class A
{
friend class Singleton<A>; //声明A的友元为单例类模板
public:
A()
{
std::cout << "begin...." << "\n";
}
~A()
{
std::cout << "end...." << "\n";
}
void Run()
{
std::cout << "run...." << "\n";
}
};
typedef Singleton<A> a;
int main()
{
a::Getinstance().Run();
// Singleton<A>::Getinstance().Run();
}
(3)使用可变参数模板-优化单例模式
头文件:
#pragma once
#ifndef _SINGLETON_H_
#define _SINGLETON_H_
#include <iostream>
using namespace std;
template <typename T>
class Singleton
{
public:
template<typename...Args>
static T &Getinstance(Args&&...args) // 外界可访问的唯一接口
{
if (instance == nullptr)
{
instance = new T(std::forward<Args>(args)...); // 初始化实例
atexit(DestroyInstance); // 当程序结束时析构
}
return *instance;
}
private:
static void DestroyInstance()
{
delete instance;
instance = nullptr;
}
Singleton();
~Singleton();
Singleton(const Singleton<T> &) {}
Singleton<T> &operator=(const Singleton<T> &) {}
static T *instance; // 私有的静态指针,指向唯一的实例,也可以用智能指针,定义为智能指针时不需要手动释放 static std::shared_ptr<T>...
};
template <class T>
T *Singleton<T>::instance = nullptr; // 初始化
#endif
程序:
#include "Singleton.h"
using namespace std;
struct A
{
friend class Singleton<A>;
A(const string &)
{
cout << "A1" << "\n";
}
A(string &&x)
{
cout << "A2" << "\n";
}
};
int main()
{
string str = "a";
Singleton<A>::Getinstance(str);
}