设计模式
GoF设计模式清单
工厂模式和抽象工厂模式在实际开发中使用较少;
单例模式是重点,针对一个类的唯一实例;
单例模式:通过设计的接口getInstance()获得对象,在接口内部设计只能有一个对象。
将类的构造函数定义为私有属性【外部不能创建对象】;定义一个私有的类的静态私有成员变量;提供一个公有的静态方法,负责一次性实例化或返回实例对象。
单例模式最推荐的方式:局部静态变量
template<typename T>
class Singlenton
{
public:
static T& GetInstance()
{
//变量初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束
//解决方式:加锁,在一个线性执行的时候,先禁用其他的线程??
static T instance;
//返回的是一个引用,不是指针,是因为指针会被delete,在下续使用中可能被delete,出现BUG。
return &instance;
}
Singlenton(T&&) = delete;
Singlenton(const T&) = delete;
void operator = (const T&) = delete;
protected:
//将类的构造函数定义为私有属性【外部不能创建对象
Singlenton() = default;
virtual ~Singlenton() = default;
}
传统的单例模式-饿汉式:一次加载完,加载类的时候速度比较慢,但以后获的对象时的速度比较快;
传统的单例模式-懒汉式:延迟加载机制,需要这个对象的时候才去实例化,加载类的时候速度较快,但以后获得对象时的速度较慢。该对象在整个应用的生命周期只有一部分时间占用资源。
(存在线程安全)面临多线程访问的安全性问题,需要做双重锁定处理才能保证安全。
双重锁定好处:在加锁前检查锁标志,用于减少锁的开销。
双重锁定问题:
/new的步骤:1.申请一块内存。2.执行构造函数对其进行初始化。3.将地址赋值给 ptr。
但是实际上编译器对指令进行重新排序之后的实现步骤是这样的:1. 申请一块内存。2.将地址赋值给 ptr。3.执行构造函数对其进行初始化。
由于线程的切换是按指令切换的,也就是说在执行这一个指令之前或者之后切换到另一个线程,那么当线程 A 在执行完前两步(1.申请一块内存。2.将地址赋值给 ptr) 的时候,此时 ptr 已经有了值,那么此时切换到线程 B 的时候,判断 ptr 不为 NULL,然后将其返回,那么线程 B 所得到的 ptr 是一个还没有进行初始化的一个 对象。
结合代码看:
KSingletonLazy* KSingletonLazy::getInstance()
{
//但是还没调用构造函数的时候,线程B执行到第一个if(),判断m_single此时不为空,则返回该变量,
//然后调用该对象的函数,但是该对象还没有进行构造。
if (m_single == nullptr)
{
m_mutex(lock);
if (m_single == nullptr)
{
//双锁的线程安全问题:不同的编译器表现是不一样的。可能先将该内存地址赋给m_single,然后再调用构造函数。
//这时线程A恰好申请完成内存,并且将内存地址赋给m_single,
m_single = new KSingletonLazy; //这条语句做了三件事:第一件事申请一块内存,第二件事调用构造函数,第三件是将该内存地址赋给m_single。
}
m_mutex(lock);
}
return m_single;
}
建造者模式:在实际开发中,在不影响代码可读性的前提下,要尽量避免继承,组合优于继承
原型模式:针对被实例化的类
设计模式原则
题外tips
避免不必要的for(),减少开销,程序如果不能运行,就让其尽早崩溃,释放内存。
使用智能指针也会存在一定的内存开销,因为要维护一个计数器;
生存周期:维护生存周期的小技巧:使用缩进;
内存泄漏:指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。最终的结果就是导致OOM。
也就是new完使用完了以后不能delete,结果自己也不能访问,系统也不能进行分配。
内存泄漏的三种情况:
1,指针重新赋值
2,错误的内存释放
3,返回值的不正确处理
程序崩溃的三种情况:
1、分段错误:尝试访问系统中不存在的内存位置;在只读存储器位置上进行写操作;堆栈溢出,无法终止内存位置的递归;
2、缓冲区溢出:程序在将数据写入缓冲区时会溢出缓冲区的边界并覆盖相邻的内存位置。
3、内存泄露:如上↑
查看内存空间 :调试-窗口-内存-xxx
工厂模式
简单工厂模式:常用&实用
定义一个基类的好处:便于实现面向对象的多态。
抽象工厂模式
组合优于继承:entity component
--继承优点:父类的大部分功能可以通过继承关系自动进入子类;修改或扩展继承而来的属性和方法较为容易。
--继承缺点:一个类只能有一个父类,不能同时继承两个父类,因此无法通过继承的方式,重用多个类中的代码;父类的属性和方法,子类是无条件继承的,容易造成方法的污染。
用”has-a”(有什么或用什么)去替代”is-a”(是什么)
--组合优点:用组合代替继承,把一些特征和行为抽取出来,形成工具类。然后通过组合成为当前类的属性。再调用其中的属性和行为达到代码重用的目的。
--组合优点:可以选择一个类中是否应该具有某种行为,从而决定应该聚合那些类,不应该聚合那些类。这样,通过聚合/组合关系,也可以避免继承所带的方法污染问题
代理模式
远程代理:为网络上的对象创建一个局部对象,所有网络通讯操作交给代理去做,让客户可以会忽略这些被代理的对象是不是远程的
虚拟代理:用于开销很大,需要较长时间实例化的对象。
Copy-on-Write代理:虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。
智能引用代理:是指当调用真实对象时,代理处理另外一些事,比如记录对此对象的调用次数等。
C++中独有代理:一个私有类包含类的成员变量,在类中直接声明私有类的变量。最大好处是二进制兼容
//创建一个私有类包含类的成员变量
class WidgetPrivate
{
private :
int color;
int height;
}
class Widget
{
public:
widget();
private:
//在类中直接声明私有类的变量
WidgetPrivate* data;
}
调试项目
.pdb文件
标签:系列,对象,C++,基础知识,继承,single,线程,内存,构造函数 From: https://www.cnblogs.com/wj0518/p/17321104.html