单例模式C++实现
特点
保证类只有一个实例对象,且提供一个访问方法,自行生成实例
个人认为不需要析构函数,这个对象生成后虽然在堆,但指向它的指针是静态的,静态就是对象是属于类的,生成到程序结束就可以一直存在,不会内存泄漏
- 所有实现方式都要注意的点
- 私有构造函数不允许用户创建对象
- 不允许复制对象
最简单的懒汉式
class Singleton {
private:
Singleton() {
cout << "构造函数" << endl;
}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
static Singleton* m_instance;
public:
static Singleton* getInstance();
};
Singleton* Singleton::m_instance = nullptr;
Singleton* Singleton::getInstance() {
if (m_instance == nullptr) {
m_instance = new Singleton;
}
return m_instance;
}
int main() {
auto tmp = Singleton::getInstance();
return 0;
}
问题:
- 多线程情况下不能保证线程安全
线程安全的懒汉式
class Singleton {
private:
Singleton() {
cout << "构造函数" << endl;
}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
static Singleton* m_instance;
public:
static Singleton* getInstance();
static mutex mtx;
};
Singleton* Singleton::m_instance = nullptr;
mutex Singleton::mtx;
Singleton* Singleton::getInstance() {
lock_guard<mutex> lg(mtx);
if (m_instance == nullptr) {
m_instance = new Singleton;
}
return m_instance;
}
int main() {
auto tmp = Singleton::getInstance();
return 0;
}
问题:
- 这个锁在第一次创建完对象之后就没用了,造成多余的开销
双检锁懒汉式
class Singleton {
private:
Singleton() {
cout << "构造函数" << endl;
}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
static Singleton* m_instance;
public:
static Singleton* getInstance();
static mutex mtx;
};
Singleton* Singleton::m_instance = nullptr;
mutex Singleton::mtx;
Singleton* Singleton::getInstance() {
if (m_instance == nullptr) {
lock_guard<mutex> lg(mtx);
if (m_instance == nullptr) {
m_instance = new Singleton;
}
}
return m_instance;
}
int main() {
auto tmp = Singleton::getInstance();
return 0;
}
问题:
- 锁前不检查,就是上一种情况,多余开销
- 锁后不检查,lock之后和第一次创建对象之前这段时间,多线程进入第一个if判断会出现问题
- CPU可能对指令进行重排
- 执行的逻辑顺序:new分配内存-->构造函数-->赋值
- 执行的可能顺序:new分配内存-->赋值-->构造函数
- 这时候如果有一个线程在第一个if判断,就会返回一个没有调用构造函数的内存
最简洁的写法,懒汉式
class Singleton {
private:
Singleton() {
cout << "构造函数" << endl;
}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
static Singleton* m_instance;
public:
static Singleton& getInstance();
};
Singleton& Singleton::getInstance() {
static Singleton m_instance;
return m_instance;
}
int main() {
auto& tmp = Singleton::getInstance();
return 0;
}
注意:
- 返回值必须赋值给引用变量,因为两种赋值操作都是不可用的
- 函数返回值是引用,不是指针,避免用户delete。singleton的生存期应该由类本身自己管理,饥汉式懒汉式
饥汉式
class Singleton {
private:
Singleton() {
cout << "构造函数" << endl;
}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
static Singleton* m_instance;
public:
static Singleton* getInstance();
};
Singleton* Singleton::m_instance = new Singleton;
Singleton* Singleton::getInstance() {
return m_instance;
}
int main() {
auto tmp = Singleton::getInstance();
return 0;
}
注意:
- 饥汉式是程序一运行就创建对象
- 懒汉式是需要使用类对象实例才去创建对象
- 如何选择:饥汉式是空间换时间,避免需要时的创建时间开销;懒汉式是时间换空间,需要的时候在创建出来
应用场景及优缺点
注意:这里的线程安全只是指getInstance函数
优点:
- 就是单例的特点咯,只提供一个实例
- 类似包装了一个全局变量,但是它可以懒汉式延迟生成,
- 减少内存开销
缺点:
- 锁和判断产生开销,不过可以通过静态方式解决
- 违背单一职责原则(一个类只有一个引起变化的原因)
应用场景:利用到单例的这种思想
- 进程管理器
- 微信
- 项目中的管理器,我就只需要一份全局的实例管理,比如线程池,项目中也有用到一个全局管理器,可以换成单例模式实现
面向对象原则
-
单一职责原则
每个类专注做一件事
例子:类不会有太多方法、成员,更容易维护 -
里氏替换原则
子类必须能替换父类(is-a) -
依赖倒置原则
高层模块不应该依赖于低层模块,而应该依赖于抽象。抽象不应依赖于细节,细节应依赖于抽象 -
接口隔离原则
为用户提供小接口,不提供大的总的接口
例子:方法该private就private,该protected就protected,暴露用户不需要的方法,用户产生依赖就不方便修改了 -
开放封闭原则
对拓展开放,对修改封闭
应用
- 隔离变化
- 各负其责(多态)
为什么要面向对象
- 需求变化很快,需要更改代码,可复用
- 实现形式就是封装继承多态,利用这种实现形式但是并不代表你的代码就是面向对象的