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

发布于:2025-02-10 ⋅ 阅读:(77) ⋅ 点赞:(0)

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

一个问题

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

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

//单例模式
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;
	}

}

在这里插入图片描述
这样看上去问题解决了,但是:
在这里插入图片描述
我们可以在类外创建对象,这不符合我们的要求,究其原因,我们把构造函数设为了公有解决这个问题将它声明为私有就可以了。

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

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

//单例模式
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) 实现一个类保证处处只有一个实例。单例模式的核心思想是:

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

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

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

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

//单例模式
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();
	}
}

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

//单例模式
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 中,局部静态变量的初始化是线程安全的,因此可以简化代码

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;
	}
}

单例模式的两种模式

饿汉模式

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

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();

懒汉模式

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

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;

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

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

   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;
    };