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;
    };
相关推荐
zyx没烦恼7 分钟前
unordered_map和unordered的介绍和使用
开发语言·c++
敲代码的 蜡笔小新9 分钟前
【行为型之策略模式】游戏开发实战——Unity灵活算法架构的核心实现策略
unity·设计模式·c#·策略模式
_yingty_9 分钟前
Java设计模式-策略模式(行为型)
java·设计模式·策略模式
LuckyRich122 分钟前
【RabbitMq C++】消息队列组件
c++·分布式·rabbitmq
让我们一起加油好吗1 小时前
【C++】模板(初阶)
开发语言·c++·visualstudio·模板·泛型编程
Run_Teenage2 小时前
C++类和对象:运行符重载、取地址运算符重载、const 修饰的类如何作为参数
开发语言·c++
一只小bit2 小时前
More Effective C++:改善编程与设计(上)
开发语言·c++·方法·技巧
李匠20245 小时前
C++GO语言微服务之图片、短信验证码生成及存储
开发语言·c++·微服务·golang
ll77881110 小时前
C++学习之路,从0到精通的征途:继承
开发语言·数据结构·c++·学习·算法
我不想当小卡拉米10 小时前
【Linux】操作系统入门:冯诺依曼体系结构
linux·开发语言·网络·c++