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;
    };
相关推荐
Feliz Da Vida11 分钟前
[代码学习] c++ 通过H矩阵快速生成图像对应的mask
c++·学习
花好月圆春祺夏安26 分钟前
基于odoo17的设计模式详解---策略模式
设计模式·策略模式
无聊的小坏坏1 小时前
单调栈通关指南:从力扣 84 到力扣 42
c++·算法·leetcode
YOLO大师2 小时前
华为OD机试 2025B卷 - 小明减肥(C++&Python&JAVA&JS&C语言)
c++·python·华为od·华为od机试·华为od2025b卷·华为机试2025b卷·华为od机试2025b卷
看到我,请让我去学习3 小时前
OpenCV编程- (图像基础处理:噪声、滤波、直方图与边缘检测)
c语言·c++·人工智能·opencv·计算机视觉
xiaolang_8616_wjl12 小时前
c++文字游戏_闯关打怪2.0(开源)
开发语言·c++·开源
夜月yeyue12 小时前
设计模式分析
linux·c++·stm32·单片机·嵌入式硬件
收破烂的小熊猫~12 小时前
《Java修仙传:从凡胎到码帝》第四章:设计模式破万法
java·开发语言·设计模式
无小道13 小时前
c++-引用(包括完美转发,移动构造,万能引用)
c语言·开发语言·汇编·c++
FirstFrost --sy14 小时前
数据结构之二叉树
c语言·数据结构·c++·算法·链表·深度优先·广度优先