C++面试者常被问到的单例模式
单例模式,他叫创建型模式,主要说的是对象的创建方式。
先给定义:一个类,不管创建多少次对象,永远只能得到该类型的一个实例。
可以采用单例模式来设计的地方:日志模块,数据库模块
要限制这个类对象的创建个数,在C++中,首先要控制这个类的构造函数,要是用户可以随意调用这个类的构造函数那就可以随意创建对象了。
我们说的单例模式一般有两种,一种叫懒汉式,另一种叫饿汉式。
饿汉式单例模式
饿汉式:还没有获取实例对象的时候实例对象就已经产生了。很饥饿,还没等使用就已经有了
#include<iostream>
class Singleton
{
public:
//3.返回类唯一的一个实例的地址
static Singleton* getInstance()
{
return &instance;
}
private:
//1.构造函数私有化
Singleton()
{
}
//2.定义一个唯一的类的实例对象
//静态成员类外初始化
static Singleton instance;
//4.delete掉默认的拷贝构造和赋值重载运算符
Singleton(const Singleton&) = delete;
Singleton& operator= (const Singleton&) = delete;
};
Singleton Singleton::instance;
int main()
{
Singleton* p1 = Singleton::getInstance();
Singleton* p2 = Singleton::getInstance();
Singleton* p3 = Singleton::getInstance();
std::cout << p1 << " " << p2 << " " << p3 << std::endl;
return 0;
}
这个饿汉式单例模式还有一个可以小小改动的点就是,返回引用,这样也有一点好处就是使用.而不是->能少敲一次键盘,如下写法
//3.返回类唯一的一个实例的地址
static Singleton& getInstance()
{
return instance;
}
懒汉式单例模式
懒汉式:唯一的实例对象,直到第一次获取它的时候才产生。很懒惰,什么时候用什么时候有
把对象的实例化延迟到第一次获取该实例
#include<iostream>
class Singleton
{
public:
//3.返回类唯一的一个实例的地址
static Singleton* getInstance()
{
if (nullptr == instance)
{
instance = new Singleton();
}
return instance;
}
private:
//1.构造函数私有化
Singleton()
{
}
//2.定义一个唯一的类的实例对象
//静态成员类外初始化
static Singleton* instance;
//4.delete掉默认的拷贝构造和赋值重载运算符
Singleton(const Singleton&) = delete;
Singleton& operator= (const Singleton&) = delete;
};
Singleton* Singleton::instance = nullptr;
int main()
{
Singleton* p1 = Singleton::getInstance();
Singleton* p2 = Singleton::getInstance();
Singleton* p3 = Singleton::getInstance();
std::cout << p1 << " " << p2 << " " << p3 << std::endl;
return 0;
}
但是这个不是可重入函数
可重入:在任意时刻被「多次调用」(无论单线程递归调用、多线程并发调用),都能保证执行结果正确,不会因为 “状态冲突” 导致逻辑错误或数据损坏的函数。
在多线程并发执行的时候会大概率是会创建出多个对象的,不能直接在多线程环境下直接使用
所以就有了线程安全的懒汉式单例模式,也是面试问到和让手写可能性最高的一个。
#include<iostream>
#include<mutex>
std::mutex mtx;
class Singleton
{
public:
//3.返回类唯一的一个实例的地址
static Singleton* getInstance()
{
//锁加双重判断
//std::lock_guard<std::mutex> guard(mtx);锁放在这里粒度太大了,单线程环境下没必要这么频繁的加锁解锁
if (nullptr == instance)
{
std::lock_guard<std::mutex> guard(mtx);
if (nullptr == instance)
{
instance = new Singleton();
}
}
return instance;
}
private:
//1.构造函数私有化
Singleton()
{
}
//2.定义一个唯一的类的实例对象
//静态成员类外初始化
static Singleton* volatile instance;
//4.delete掉默认的拷贝构造和赋值重载运算符
Singleton(const Singleton&) = delete;
Singleton& operator= (const Singleton&) = delete;
};
Singleton* volatile Singleton::instance = nullptr;
int main()
{
Singleton* p1 = Singleton::getInstance();
Singleton* p2 = Singleton::getInstance();
Singleton* p3 = Singleton::getInstance();
std::cout << p1 << " " << p2 << " " << p3 << std::endl;
return 0;
}
这里加上volatile关键字的作用是:每个线程访问 instance 时,必须直接读取「全局内存」中的值,而不能使用线程本地缓存的旧值。这样当线程 A 把 instance 改为非 null 后,线程 B 下次判断 if (nullptr == instance) 时,能立刻读到最新值,不会重复进入临界区。
不过其实还有一种可以不加互斥锁也能实现的线程安全的懒汉式单例模式,如下
#include<iostream>
class Singleton
{
public:
//3.返回类唯一的一个实例的地址
static Singleton* getInstance()
{
//2.定义一个唯一的类的实例对象
//静态局部变量
static Singleton instance;
return &instance;
}
private:
//1.构造函数私有化
Singleton()
{
}
//4.delete掉默认的拷贝构造和赋值重载运算符
Singleton(const Singleton&) = delete;
Singleton& operator= (const Singleton&) = delete;
};
int main()
{
Singleton* p1 = Singleton::getInstance();
Singleton* p2 = Singleton::getInstance();
Singleton* p3 = Singleton::getInstance();
std::cout << p1 << " " << p2 << " " << p3 << std::endl;
return 0;
}
并且其实上边除了多线程环境可能还有内存泄露问题,new出来的这个对象没有析构函数,应该主动的手动加上一个静态成员方法Destory()去判断这个指针是不是为空然后delete析构一下,这里就比较简单了。这就是小问题了,后边打算好好全面的理解一下static相关知识点
更多推荐


所有评论(0)