C++设计模式 —— 单例模式

设计模式 ------ 单例模式

在了解C++面向对象的三大特性:封装,继承,多态之后。我们创建类的时候就有了比较大的空间。但是,我们平时在创建类的时候,不是简单写个class和继承关系就完事了的。我们写的类要在一些场景下满足一些特殊的要求。

一个问题

现在有一个具体的要求,创建一个类,保证处处只有一个实体,这个要求在平常的工作中是很常见的, 配置文件管理,日志系统,数据库连接池, 线程池等。

如何实现呢?保证只有一个实体,可以考虑静态成员变量 ,我们之前在C++继承中说过,无论继承关系有多少层,只要为静态成员,全局就只有一份。所以我们可以先从这个方向入手。

cpp 复制代码
//单例模式
class SingleClass
{
public:
	static SingleClass* GetInstance()
	{
		if (instance == nullptr)
		{
			instance = new SingleClass();
		}
		return instance;
	}
	static SingleClass* instance;
};

//静态成员在外部初始化
SingleClass* SingleClass::instance = nullptr;

int main()
{
	SingleClass* s1 = SingleClass::GetInstance();
	SingleClass* s2 = SingleClass::GetInstance();

	if (s1 == s2)
	{
		std::cout << "s1和s2为同一实体" << std::endl;
	}
	else
	{
		std::cout << "s1和s2不为同一实体" << std::endl;
	}

}

这样看上去问题解决了,但是:

我们可以在类外创建对象,这不符合我们的要求,究其原因,我们把构造函数设为了公有解决这个问题将它声明为私有就可以了。

cpp 复制代码
//单例模式
class SingleClass
{
public:
	static SingleClass* GetInstance()
	{
		if (instance == nullptr)
		{
			instance = new SingleClass();
		}
		return instance;
	}
private:
	SingleClass(){} //构造函数为私有
	static SingleClass* instance;
};

这样就可以了,但是别忘了我们还有拷贝构造和赋值拷贝,这两个也可以构造出新对象,所以为了保险可以直接把他俩禁了:

cpp 复制代码
//单例模式
class SingleClass
{
public:
	static SingleClass* GetInstance()
	{
		if (instance == nullptr)
		{
			instance = new SingleClass();
		}
		return instance;
	}
private:
	SingleClass(){} //构造函数为私有
	SingleClass(const SingleClass&) = delete; // 禁止拷贝构造
	SingleClass& operator=(const SingleClass&) = delete; // 禁止赋值操作
	static SingleClass* instance;
};

//静态成员在外部初始化
SingleClass* SingleClass::instance = nullptr;

这就是单例模式的雏形了。

单例模式(Singleton Pattern) 实现一个类保证处处只有一个实例。单例模式的核心思想是:

私有化构造函数:禁止外部直接创建对象。

静态方法获取实例:通过静态成员函数控制唯一实例的创建和访问。

禁止拷贝和赋值:防止通过拷贝构造函数或赋值操作生成新实例。

上面的代码还不能保证在多线程条件下是安全的的

cpp 复制代码
//单例模式
class SingleClass
{
public:
	static SingleClass* GetInstance()
	{
		if (instance == nullptr)
		{
			instance = new SingleClass();
		}
		return instance;
	}

	void PrintAddress()
	{
		std::cout << "地址为:" << this << std::endl;
	}
private:
	SingleClass(){} //构造函数为私有
	SingleClass(const SingleClass&) = delete; // 禁止拷贝构造
	SingleClass& operator=(const SingleClass&) = delete; // 禁止赋值操作
	static SingleClass* instance;
	//static std::mutex mtx;
};

//静态成员在外部初始化
SingleClass* SingleClass::instance = nullptr;
//std::mutex SingleClass::mtx;


// 全局互斥锁,用于保护输出
std::mutex coutMtx;

// 线程函数
void threadFunc() 
{
	SingleClass* instance = SingleClass::GetInstance();
	std::lock_guard<std::mutex> lock(coutMtx);  // 加锁保护输出
	instance->PrintAddress();
}

int main()
{
	const int threadNumber = 100;
	std::vector<thread> threadVT;

	//创建多个线程
	for (int i = 0; i < threadNumber; i++)
	{
		threadVT.emplace_back(threadFunc);
	}

	for (auto& t : threadVT)
	{
		t.join();
	}
}

大家可以试一下,可能会有不同的地址。为了保证线程安全,我们还得加锁

cpp 复制代码
//单例模式
class SingleClass
{
public:
	static SingleClass* GetInstance()
	{
		if (instance == nullptr)
		{
			std::lock_guard<std::mutex> lock(mtx);  // 加锁
			instance = new SingleClass();
		}
		return instance;
	}

private:
	SingleClass(){} //构造函数为私有
	SingleClass(const SingleClass&) = delete; // 禁止拷贝构造
	SingleClass& operator=(const SingleClass&) = delete; // 禁止赋值操作
	static SingleClass* instance;
	static std::mutex mtx;
};

//静态成员在外部初始化
SingleClass* SingleClass::instance = nullptr;
std::mutex SingleClass::mtx;

C++11后代写法

在 C++11 中,局部静态变量的初始化是线程安全的,因此可以简化代码

cpp 复制代码
class SingleClass
{
public:
	static SingleClass& GetInstance()
	{
		static SingleClass instance;
		return instance;
	}
private:
	SingleClass() {};
	SingleClass(const SingleClass&) = delete; // 禁止拷贝构造
	SingleClass& operator=(const SingleClass&) = delete; // 禁止赋值操作
};

int main()
{
	SingleClass& s1 = SingleClass::GetInstance();
	SingleClass& s2 = SingleClass::GetInstance();

	if (&s1 == &s2)
	{
		std::cout << "s1和s2同一对象" << std::endl;
	}
	else
	{
		std::cout << "s1和s2不为同一对象" << std::endl;
	}
}

单例模式的两种模式

饿汉模式

饿汉模式讲究的是对象已经创建好,要用的时候直接拿就行

cpp 复制代码
class SingleClass
{
public:
	static SingleClass* GetInstance()
	{
		return instance;
	}
private:
	SingleClass() {}
	SingleClass(const SingleClass&) = delete; // 禁止拷贝构造
	SingleClass& operator=(const SingleClass&) = delete; // 禁止赋值操作
	static SingleClass* instance;
};

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

懒汉模式

懒汉模式讲究的是对象要用时才创建

cpp 复制代码
class SingleClass
{
public:
	static SingleClass* GetInstance()
	{
		if (instance == nullptr)
		{
			instance = new SingleClass();
			return instance;
		}
	}
private:
	SingleClass() {}
	SingleClass(const SingleClass&) = delete; // 禁止拷贝构造
	SingleClass& operator=(const SingleClass&) = delete; // 禁止赋值操作
	static SingleClass* instance;
};

SingleClass* SingleClass::instance = nullptr;

这个和我们最开始写的代码差不多,要改进的话还要保证线程安全。

我们可以将这种思想放到我们实际的代码中,比如我们日志系统的开发:

cpp 复制代码
   class LoggerManager
    {
    public:
        static LoggerManager &getInstance()
        {
            // c++11之后,针对静态局部变量,编译器在编译的层面实现了线程安全
            // 当静态局部变量在没有构造完成之前,其他的线程进入就会阻塞
            static LoggerManager eton;
            return eton;
        }
        void addLogger(logs::logger::ptr &logger)
        {
            if (hasLogger(logger->name()))
                return;
            std::unique_lock<std::mutex> lock(_mutex);
            _loggers.insert(std::make_pair(logger->name(), logger));
        }
        bool hasLogger(const std::string &name)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _loggers.find(name);

            if (it == _loggers.end())
            {
                return false;
            }

            return true;
        }
        logs::logger::ptr getLogger(const std::string &name)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _loggers.find(name);

            if (it == _loggers.end())
            {
                return logger::ptr();
            }

            return it->second;
        }

        logs::logger::ptr rootLogger()
        {
            return _root_logger;
        }

    private:
        LoggerManager()
        {
            std::unique_ptr<logs::LoggerBuilder> builder(new logs::LocalloggerBuild());
            builder->buildLoggerName("root");

            _root_logger = builder->build();
            _loggers.insert(std::make_pair("root", _root_logger));
        }

    private:
        std::mutex _mutex;
        logs::logger::ptr _root_logger; // 默认日志器
        std::unordered_map<std::string, logger::ptr> _loggers;
    };
相关推荐
cchjyq1 小时前
opencv:基于暗通道先验(DCP)的内窥镜图像去雾
java·c++·图像处理·人工智能·opencv·计算机视觉
计算机视觉-Archer1 小时前
[NKU]C++安装环境 VScode
开发语言·c++
源代码•宸1 小时前
Leetcode—252. 会议室【简单】Plus
c++·经验分享·算法·leetcode·排序
Golinie2 小时前
【C++高并发服务器WebServer】-14:Select详解及实现
linux·服务器·c++·select·webserver
周杰伦fans3 小时前
DWORD 和 QWORD
c++
奇变偶不变07274 小时前
【C/C++】每日温度 [ 栈的应用 ] 蓝桥杯/ACM备赛
c语言·开发语言·数据结构·c++·算法·蓝桥杯
沉到海底去吧Go4 小时前
【PDF提取内容】如何批量提取PDF里面的文字内容,把内容到处表格或者批量给PDF文件改名,基于C++的实现方案和步骤
数据库·c++·pdf·excel·pdf信息提取到表格·多个区域内容提取信息到表格·批量pdf多个区域内容保存表格
心.c5 小时前
打家劫舍3
c++·算法·动态规划
一个处女座的程序猿O(∩_∩)O6 小时前
React 设计模式:实用指南
前端·react.js·设计模式
扫地僧0096 小时前
第17章 读写锁分离设计模式(Java高并发编程详解:多线程与系统设计)
java·前端·设计模式