目录
1--单例设计模式
单例设计模式要求某一个类最多创建一个对象,这个对象即单例对象(全局唯一实例对象);
单例设计模式可以分为:① 懒汉式(当需要实例时才去加载);② 饿汉式(程序一开始就加载这个实例);
若一个类对象比较复杂和庞大,并且是可以复用的,当频繁地去创建和销毁类对象时就会造成巨大的性能浪费,通过单例设计模式将这个类对象设计为单例对象可以解决上述问题;
一般推荐使用嵌套类的方式来回收内存,以下代码展示了使用嵌套类来销毁对象和回收内存的方法,并采用了懒汉式的设计模式;
#include <iostream>
class MyCAS{
public:
static MyCAS *GetInstance(){
if(m_instance == NULL){
m_instance = new MyCAS();
static CGar c1;
}
return m_instance;
}
void func(){
std::cout << "test sample!" << std::endl;
}
class CGar{ // 类中套类,用于释放对象
public:
~CGar(){
if(MyCAS::GetInstance){
delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
};
private:
MyCAS(){}; // 私有化成员变量,不能通过构造函数来创建对象
static MyCAS *m_instance; // 静态成员变量
};
// 静态数据成员类外初始化
MyCAS* MyCAS::m_instance = NULL;
int main(int argc, char *argv[]){
MyCAS *sample1 = MyCAS::GetInstance(); // 创建对象,返回该类对象的指针
MyCAS *sample2 = MyCAS::GetInstance(); // 创建对象,返回该类对象的指针
std::cout << sample1 << " " << sample2 << std::endl; // 两个指针指向同一个对象
sample1->func(); // 调用成员函数测试
return 0;
}
2--双重检查锁
双重检查锁可以避免多个线程发生数据竞争的情况,在下面的代码中使用双重检查锁可以避免多个线程同时创建类对象的情况(即执行 new MyCAS(););
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mutex1; // 互斥量
class MyCAS{
public:
static MyCAS *GetInstance(){
// 双重检查锁
if(m_instance == NULL){
std::unique_lock<std::mutex> guard1(mutex1); // 自动加锁和解锁
if(m_instance == NULL){
m_instance = new MyCAS();
static CGar c1;
}
}
return m_instance;
}
void func(){
std::cout << "test sample!" << std::endl;
}
class CGar{ // 类中套类,用于释放对象
public:
~CGar(){
if(MyCAS::GetInstance){
delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
};
private:
MyCAS(){}; // 私有化成员变量,不能通过构造函数来创建对象
static MyCAS *m_instance; // 静态成员变量
};
// 静态数据成员类外初始化
MyCAS* MyCAS::m_instance = NULL;
// 线程入口函数
void mythread(){
std::cout << "start thread" << std::endl;
MyCAS *p_a = MyCAS::GetInstance();
p_a->func();
std::cout << "thread end" << std::endl;
return;
}
int main(int argc, char *argv[]){
std::thread thread1(mythread);
std::thread thread2(mythread);
thread1.join();
thread2.join();
return 0;
}
双重检查锁存在的问题:
当一个类对象中某些成员比较复杂,初始化时间比较长;当一个线程创建单例对象时,其部分成员未来得及初始化;这时候若其他线程发现了单例对象已被创建(已有地址空间),就可能会选择调用类对象的成员函数(但这时复杂的成员函数未初始化),就会出现错误;
这时需要使用 volatile 关键字来确保被调用的成员函数被完全初始化;
3--std::call_once()的使用
std::call_once() 的功能是确保函数 func() 只会被调用一次;(即 std::call_once() 具有互斥量的能力,相对于互斥量其消耗的资源更少)
std::call_once() 需要和一个标记进行结合使用,这个标记决定对象的函数 func() 是否被调用;
#include <iostream>
#include <thread>
#include <mutex>
std::once_flag g_flag; // 系统定义的标记
class MyCAS{
private:
static void CreateInstance(){
m_instance = new MyCAS();
static CGar c1;
}
public:
static MyCAS *GetInstance(){
// std::call_once 确保创建单例对象的函数只会被调用一次
std::call_once(g_flag, CreateInstance);
return m_instance;
}
void func(){
std::cout << "test sample!" << std::endl;
}
class CGar{ // 类中套类,用于释放对象
public:
~CGar(){
if(MyCAS::GetInstance){
delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
};
private:
MyCAS(){}; // 私有化成员变量,不能通过构造函数来创建对象
static MyCAS *m_instance; // 静态成员变量
};
// 静态数据成员类外初始化
MyCAS* MyCAS::m_instance = NULL;
// 线程入口函数
void mythread(){
std::cout << "start thread" << std::endl;
MyCAS *p_a = MyCAS::GetInstance();
p_a->func();
std::cout << "thread end" << std::endl;
return;
}
int main(int argc, char *argv[]){
std::thread thread1(mythread);
std::thread thread2(mythread);
thread1.join();
thread2.join();
return 0;
}