1.单例模式
1°定义
之前已经学过一些设计模式
迭代器模式 -- 基于面向对象三大特性之一的 封装设计出来的 用一个迭代器类封装以后
不暴露容器结构的情况下 统一的方式访问修改容器中的数据
适配器模式 -- 体现的是一种复用
还有一些常见的设计模式如:工厂模式 装饰器模式 观察者模式 单例模式...
单例模式:一个类只能在全局(进程中)只有一个实例对象
什么场景下会使用?
比如一个进程中有一个内存池 进程中的多线程需要内存都要到这个内存池中取 那么这个内存池的类就可以设计单例模式
类的名称是随着你的场景给的 比如你的是内存池 那么你就定义成MemoryPool
2°懒汉模式
懒汉模式--第一次获取对象时 再创建对象
#include <iostream>
#include <thread>
#include <vector>
#include <Windows.h>
#include <mutex>
using namespace std;
namespace Lazy_Man
{
//懒汉模式--第一次获取对象时 再创建对象
class Singleton
{
public:
static Singleton* GetInstance()
{
//Sleep(1000);//增加没加锁时出现线程不安全的条件(2个以上线程同时过了判断条件)
//如果有两个线程同时new 最后就不是只有一个对象了
//加锁解决
//_mtx.lock();
if (_pinst == nullptr)//后一波线程来的时候就直接返回了
{
unique_lock<mutex> lock(_mtx);
//出作用域后解锁 但后面还有代码不想保护 添加{}局部域
//加锁只用保护第一次 需要优化
//只要_pinst已经指向了new出来的实例对象 就不需要加锁了
//可以分析出现在的代码 存在优化空间
//双检查即可
if (_pinst == nullptr)
{
_pinst = new Singleton;
}
}
//_mtx.unlock();
//第一次给一个值
//后面每次获取的对象就都是一样的
return _pinst;
}
static void DelInstance()
{
unique_lock<mutex> lock(_mtx);
delete _pinst;
_pinst = nullptr;
}
private:
Singleton()
{}
Singleton(const Singleton& s) = delete;
static Singleton* _pinst;//静态的 每次过来都会是第一次的对象
static mutex _mtx;
};
//1.如果要手动释放单例对象 可以调用DelInstance
//2.如果需要程序结束时 正常释放单例对象 可以加入下面的设计
class GC
{
public:
~GC()
{
Singleton::DelInstance();
}
};
static GC gc;
//定义一个全局的静态对象
//main函数结束后会调用析构函数
Singleton* Singleton::_pinst = nullptr;
mutex Singleton::_mtx;
}
int main()
{
//Singleton s1;
//Singleton s2;
//------------
//cout << Singleton::GetInstance() << endl;
//cout << Singleton::GetInstance() << endl;
//cout << Singleton::GetInstance() << endl;
//---------------
//拷贝构造出来的是新的对象 拷贝构造也要禁掉
//Singleton copy(*Singleton::GetInstance());
vector<std::thread> vthreads;
int n = 10;
for (int i = 0; i < n; ++i)
{
vthreads.push_back(std::thread([]()
{
//cout << std::this_thread::get_id() << ":";
cout << Lazy_Man::Singleton::GetInstance() << endl;
}));
}
for (auto& t : vthreads)
{
t.join();
}
//有线程安全的问题
//加锁
return 0;
}
3°饿汉模式
namespace Hungry_Man
{
//饿汉模式 一开始(main函数之前)就创建对象
class Singleton
{
public:
static Singleton* GetInstance()
{
return &_inst;
}
Singleton(const Singleton&) = delete;
private:
Singleton()
{}
static Singleton _inst;
};
Singleton Singleton::_inst;
//static对象是在main函数之前创建的 这回只有主线程 所以不存在线程安全问题
int test()
{
//Singleton s1;
//Singleton s2;
//------------
//cout << Singleton::GetInstance() << endl;
//cout << Singleton::GetInstance() << endl;
//cout << Singleton::GetInstance() << endl;
//---------------
//拷贝构造出来的是新的对象 拷贝构造也要禁掉
//Singleton copy(*Singleton::GetInstance());
vector<std::thread> vthreads;
int n = 10;
for (int i = 0; i < n; ++i)
{
vthreads.push_back(std::thread([]()
{
//cout << std::this_thread::get_id() << ":";
cout << Singleton::GetInstance() << endl;
}));
}
for (auto& t : vthreads)
{
t.join();
}
//有线程安全的问题
//加锁
return 0;
}
}
int main()
{
Hungry_Man::test();
//一开始就创建好了 没有线程安全问题
return 0;
}
4°总结
总结对比一下饿汉和懒汉的区别
- 懒汉模式需要考虑线程安全和释放的问题 实现相对复杂 饿汉模式不存在以上问题 实现简单
- 懒汉是一种懒加载模式 需要时再初始化对象 不会影响程序的启动 饿汉模式则相反 程序启动阶段就创建初始化实例对象 会导致程序启动慢 影响体验
- 如果有多个单例类 假设有依赖关系(B依赖A) 要求A单例先创建初始化 B再启动 那么就不能用饿汉 因为无法保证初始化顺序 这个用懒汉我们就可以手动控制
总结一下:实际中懒汉模式还是更实用一些
2.类型转换
1°C/C++
int main()
{
int i = 1;
double d = 8.88;
i = d;//类型转换 C语言支持相近类型的隐式类型转换(相近类型 也就是意义相似的类型)
cout << i << endl;
int* p = nullptr;
p = (int*)i;//C语言支持相近类型的强制类型转换(不相近类型 也就是意义差别很大的类型)
cout << p << endl;
//C++ 兼容C语言留下来的隐式转换和显示转换 但是C++觉得C语言做得不规范 C++想兼容一下
//引入了四种
//static_cast,reinterpret_cast,const_cast,dynamic_cast
d = static_cast<double>(i); //对应C语言隐式类型转换(相近类型)
p = reinterpret_cast<int*>(i);//对应C语言大部分强制类型转换(不想近类型)
//const int ci = 10;//加volatile可防止优化const
volatile const int ci = 10;
//int* pi = (int*)&ci;//C语言
int* pi = const_cast<int*>(&ci);//对应C语言强制类型转换中去掉const属性(不相近类型)
*pi = 20;
cout << *pi << endl;//20
cout << ci << endl;//10 这里的打印10是因为ci存储的内存被改了 但是ci被放进了寄存器 这里去寄存器中取
//还是19 本质是由于编译器对const对象存取优化机制导致
//const变量先放进寄存器 内存当中改成了20 但取ci的时候是去寄存器里取得 所以还是10
//想要禁止编译器做这个优化 每次都到内存中取值 就把volatile加上
return 0;
}
static_cast、reinterpret_cast、const_cast、dynamic_cast
2°static_cast
static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用st
atic_cast,但它不能用于两个不相关的类型进行转换
对应隐式类型转换
3°reinterpret_cast
reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型
转换为另一种不同的类型
对应强制类型转换
4°const_cast
const_cast最常用的用途就是删除变量的const属性,方便赋值
去掉const属性
5°dynamic_cast
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
向下转型:父类对象指针/引用-子类指针/引用(用dynamic_cast转型是安全的)
注意:
- dynamic_cast只能用于含有虚函数的类
- dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
class A
{
public:
virtual void f()
{}
protected:
int _a;
};
class B :public A
{
protected:
int _b;
};
void f_cast(A* pa)
{
//如果想区分pa是指向父类还是子类对象?
//B* pb = (B*)pa;
//强转可能出问题
//如果pa指向的是父类对象
//只会多看4个字节
//但看不到_b
B* pb = dynamic_cast<B*>(pa);
//如果pa指向子类对象 则转换成功
//如果pa指向父类对象 则转换失败 返回nullptr
if (pb != nullptr)
{
cout << "转换成功:pa指向子类对象" << endl;
//pb是指向子类 pa也指向子类 是可以转换的
}
else
{
cout << "转换失败:pa指向父类对象" << endl;
}
}
int main()
{
A a;
B b;
//C++子类对象可以赋值给父类的对象 指针 引用
//这个过程会发生切片 这个过程是语法天然支持的 这个叫向上转换 都可以成功
//如果是父类的指针或者引用 传给子类的指针 这个过程叫向下转换 这个过程有可能能成功
//要看具体情况
//最后需要注意的是:dynamic_cast向下转换只能针对继承中的多态类型(父类必须包含虚函数)
//dynamic_cast如何识别父类的指针是指向父类对象还是子类对象的呢?
//dynamic_cast的原理:dynamic_cast通过去找虚表的上方存储的标识信息
//来判断指向父类对象还是子类对象
A* pa = &a;
f_cast(pa);
pa = &b;
f_cast(pa);
return 0;
}
6°explicit
explicit关键字阻止经过转换构造函数进行的隐式转换的发生
class A
{
public:
explicit A(int a)
{
cout << "A(int a)" << endl;
}
explicit A(int a1, int a2)
{
cout << "A(int a1, int a2)" << endl;
}
A(const A & a)
{
cout << "A(const A& a)" << endl;
}
private:
int _a1;
int _a2;
};
int main()
{
A a1(1);
//隐式转换-> A tmp(1); A a2(tmp);
//再优化成直接构造
//A a2 = 1;
//多个参数也可以防止隐式类型转换
//A a3 = { 1,2 };//C++11
}
【C++】22.单例模式+类型转换 完