智能指针
智能指针并非指针,他是一个栈上的类对象。这个对象托管了堆区的资源。
智能指针给我带来的意义是什么及背后的机制:
智能指针所使用的机制是什么呢?
当栈上的智能指针对象出栈时,所托管的堆区对象自动进行析构。
用到的知识点就是栈上对象出栈自动销毁的特性,因为出栈即自动调用析构。
RAII 是 resource acquisition is initialization 的缩写,意为“资源获取即初始化”。它是 C++ 之父 Bjarne Stroustrup 提出的设计理念,其核心是把资源和对象的生命周期绑定,对象创建获取资源,对象销毁释放资源。在 RAII 的指导下,C++ 把底层的资源管理问题提升到了对象生命周期管理的更高层次。
那么到底什么是 RALL 机制?
当我们 new 出一块内存空间,在使用完之后,如果不使用 delete 来释放这块资源则将导致内存泄露,这在中大型项目中是极具破坏性的。但是人无完人,我们并不能保证每次都记得释放无法再次获取到且不再使用的内存。
1.auto_ptr资源所有权转移智能指针。
手动实现资源转移智能指针的底层逻辑。
#include <iostream>
#include <memory>
using namespace std;
class Stu
{
private:
string name;
int age;
public:
Stu(string name, int age)
{
this->name = name;
this->age = age;
cout << "Stu的构造" << endl;
}
~Stu()
{
cout << "Stu的析构" << endl;
}
void showInfo()
{
cout << "姓名:" << this->name << ",年龄:" << this->age << endl;
}
};
template <class T>
class Auto_ptr
{
private:
//1.定义一根指针。
T* ptr;
public:
//2.构造函数中对这根指针进行初始化,指向堆区资源。
Auto_ptr(T* _p = nullptr)
{
this->ptr = _p;
}
//3.在析构的逻辑中书写释放堆区资源的逻辑。
~Auto_ptr()
{
delete ptr;
ptr = nullptr;
}
//4.资源所有权转移逻辑。
Auto_ptr(const Auto_ptr& other)
{
this->ptr = other.ptr;
const_cast<Auto_ptr&>(other).ptr = nullptr;
}
//5.资源所有权转移逻辑。
Auto_ptr& operator=(const Auto_ptr& other)
{
if(this->ptr != nullptr)
{
delete ptr;
ptr = nullptr;
}
this->ptr = other.ptr;
}
T* get()
{
return ptr;
}
T* operator->()
{
return ptr;
}
};
int main()
{
//智能指针对象托管堆区资源对象。
Auto_ptr<Stu> ptr(new Stu("zhangsan",18));
ptr.get()->showInfo();
//智能指针对象调用堆区对象的成员方法。
ptr->showInfo();
cout << "--------------------------------" << endl;
Auto_ptr<Stu> ptr1 = ptr;
ptr1->showInfo();
ptr->showInfo();//ptr 已经转移资源访问失败 这也是它的弊端
return 0;
}
2.带有引用记数器的共享智能指针shared_ptr(重点)
#include <iostream>
#include <memory>
using namespace std;
class Stu
{
private:
string name;
int age;
public:
Stu(string name, int age)
{
this->name = name;
this->age = age;
cout << "Stu的构造" << endl;
}
~Stu()
{
cout << "Stu的析构" << endl;
}
void showInfo()
{
cout << "姓名:" << this->name << ",年龄:" << this->age << endl;
}
};
//实现一个Auto_ptr的资源所有权转移智能指针。
template <class T>
class Auto_ptr
{
private:
T* ptr;
public:
//当这个智能指针对象被构造时,类中属性指针指向堆区。
Auto_ptr(T* _p = nullptr)
{
this->ptr = _p;
}
~Auto_ptr()
{
//当出栈之后自动销毁。
delete ptr;
ptr = nullptr;
}
//实现auto_ptr的拷贝构造。
Auto_ptr(const Auto_ptr& other)
{
this->ptr = other.ptr;
const_cast<Auto_ptr&>(other).ptr = nullptr;
}
//实现=号运算符重载的相关逻辑。
Auto_ptr& operator=(const Auto_ptr& other)
{
//如果这个智能指针对象指向另外一块堆区资源,则先释放托管的堆区资源。
if(this->ptr != nullptr)
{
delete ptr;
ptr = nullptr;
}
//然后,再进行拷贝赋值传递。
this->ptr = other.ptr;
}
//获取指向堆区资源的逻辑。
T* get()
{
return ptr;
}
//获取指针运算符的逻辑。
T* operator->()
{
return ptr;
}
};
//实现一个引用记数器的类模板。
template <class T>
class RefCount
{
private:
//也得有资源时才加+1所以也得有一根指针
T* ptr;
//也有记数有用的变量。
int count;
public:
//引用记数器的构造。
RefCount(T* p = nullptr)
{
this->ptr = p;
if(ptr != nullptr)
{
this->count = 1;
}
//cout << "引用记数器被构造了出来" << endl;
}
~RefCount()
{
//cout << "引用记数器被释放了" << endl;
}
//引用记数+1功能
void addRef()
{
this->count++;
}
//引用记数-1功能
int delRef()
{
return --count;
}
//获取引用记数。
int get_count()
{
return this->count;
}
};
//使用类模板实现一个共享智能指针。
template <class T>
class Shared_ptr
{
private:
//定义两个属性:指向堆区资源的指针及引用记数器指针。
T* ptr;
RefCount<T>* refcount;
public:
Shared_ptr(T* p = nullptr)
{
//为类中指向堆区的指针赋初值。
this->ptr = p;
//如果类中指针指向了堆区,则定义一个引用记数器。
if(ptr != nullptr)
{
//使用new在堆区定义一个引用记数对象。
refcount = new RefCount<T>(ptr);
}
}
//共享智能指针的析构逻辑。
~Shared_ptr()
{
//当每一个智能指针在栈上被销毁时,进入析构后,引用记数就-1,一直到0,即意味着没有任何一个指针托管堆区资源了。
//那么就释放堆区资源。并将指针指向空。
if(refcount->delRef() == 0)
{
delete ptr;
delete refcount;
ptr = nullptr;
refcount = nullptr;
}
}
//共享智能指针的拷贝构造。
Shared_ptr(const Shared_ptr& other)
{
//发生拷贝时,即把别外一个对象中的指针与引用记数器全部拷贝过来。
this->ptr = other.ptr;
this->refcount = other.refcount;
//同时引用记数要加1;
refcount->addRef();
}
//共享智能指针的=号运算符重载函数
Shared_ptr& operator=(const Shared_ptr& other)
{
//如果出现自我赋值的情况则把本对象返回。
if(this == &other)
{
return *this;
}
//如果本类中的指针是指向了另外一块堆区资源,则首先要把指向另外一块堆区的资源进行释放。
//然后再把另一个智能指针对象中的指针与引用记数拷贝过来。
if(this->ptr != nullptr)
{
delete this->ptr;
delete this->refcount;
this->ptr = other.ptr;
this->refcount = other.refcount;
refcount->addRef();
}
//如果这是一个空的智能指针那么就把另外一对象为本对象进行赋值就好了。
else {
this->ptr = other.ptr;
this->refcount = other.refcount;
refcount->addRef();
}
return *this;
}
//获取引用资源的记数
int use_count()
{
return refcount->get_count();
}
//实现指针运算符重载函数的接口
T* operator->()
{
return this->ptr;
}
};
int main()
{
//智能指针对象托管堆区资源对象。
// Auto_ptr<Stu> ptr(new Stu("zhangsan",18));
// ptr.get()->showInfo();
//智能指针对象调用堆区对象的成员方法。
// ptr->showInfo();
// cout << "--------------------------------" << endl;
// Auto_ptr<Stu> ptr1 = ptr;
// ptr1->showInfo();
// ptr->showInfo();
Shared_ptr<Stu> ptr(new Stu("zhangsan",20));
Shared_ptr<Stu> ptr1 = ptr;
ptr->showInfo();
ptr1->showInfo();
Shared_ptr<Stu> ptr2;
ptr2 = ptr1;
ptr2->showInfo();
cout << ptr2.use_count() << endl;
return 0;
}
3、智能指针shaared_ptr存在问题及解决方案:黄金搭档weak_ptr
当使用shared_ptr出现环状引用时,会造成引用记数也+1的情况,这样就出现在析构时,引用记数不会到达==0这个条件,所以就会出内存资源的泄漏。
此时应该使用弱引用智能指针,打断这种环状引用。因为弱引用智能指针不改变资源的引用记数。
在使用智能指针直接托管堆区资源时,使用强引用智能指针shared_ptr; 也就是说智能指针对象后面跟着new的时候就是直接托管。
当资源的指针被间接赋值或引用时,使用弱引用智能指针,这样避免资源的泄漏。
应用实例:
#include <iostream>
#include <memory>
using namespace std;
class B;
class A
{
public:
weak_ptr<B> ptr_b;
public:
A()
{
cout << "A的构造" << endl;
}
~A()
{
cout << "A的析构" << endl;
}
//定义一个A中槽函数
void slot_function()
{
cout << "22051班同学~~!!!加油!!!!" << endl;
}
};
class B
{
public:
weak_ptr<A> ptr_a;
public:
B()
{
cout << "B的构造" << endl;
}
~B()
{
cout << "B的析构" << endl;
}
//定义一B中信号函数。
void single()
{
shared_ptr<A> temp_ptr = ptr_a.lock();
if(temp_ptr == nullptr)
{
return;
}
else {
temp_ptr->slot_function();
}
}
};
int main()
{
shared_ptr<A> pA(new A); //pA + 1;
shared_ptr<B> pB(new B);//pB + 1;
pA->ptr_b = pB;
pB->ptr_a = pA;
//使用pB调用类中的成员函数:信号函数时,是不是就是pA中的槽函数自动执行。
pB->single();
return 0;
}
4.独占智能指针unique_ptr
//unique,英译:独一无二的,唯一的智能指针,他是不共享的,他不想其它的共享对象也操作他的指向的堆区空间,他是唯一一个可操作这个堆区空间的对象。也就是说他的类对象是不可以发生拷贝构造与赋值运算符重载的。
unique_ptr<A> ptr(new A(1));
ptr.get()->Info();
//那么我们如何实现一下他的内部实现呢?因为是系统提供的,那如何我让他没有呢?
//其实应该有两种,1种是把这个拷贝构造与赋值运算符函数设为私有。第2种就是把他给删除了。
//系统给我提供了一个关键字可以把他给删除了。在函数名后面加上 = delete;
即:
unique_ptr(const unique_ptr& ptr) = delete;
A& operator=(const unique_ptr& ptr) = delete;
5.智能指针与自定义删除器及包装器的使用:
#include <iostream>
#include <memory>
#include <functional>
using namespace std;
template <class T>
//定义一个自定义的删除器:
class Deleter
{
public:
void operator()(T* p)
{
delete [] p;
}
};
class A
{
public:
A()
{
cout << "A的构造" << endl;
}
~A()
{
cout << "A的析构" << endl;
}
};
//使有全局函数指针来做为删除策略。
template <class T>
void deletor(T* p) //void (T*)
{
delete []p;
}
void showInfo()
{
cout << "22051班同学!!!!加油!!!" << endl;
}
int main()
{
//使用自定义函数对象来现实shared_ptr的定制删除器。
//shared_ptr<A> ptr(new A[5],Deleter<A>());
//使用匿名函数对象Lambda表达式
// shared_ptr<A> ptr(new A[5],[](A* p){
// delete []p;
// });
//独一智能指针的C++官方提供的释放连续空间的删除器。
//unique_ptr<A,default_delete<A[]>> ptr(new A[5]);
//使用自定义删除器。
//unique_ptr<A,Deleter<A>> ptr(new A[5]);
//1.包装全局函数为一个自定义删除器类型。
//unique_ptr<A,function<void (A*)>> ptr(new A[5],deletor<A>);
//2.包装一个匿名函数对象的类型。
// unique_ptr<A,function<void (A*)>> ptr(new A[5],[](A*p){
// delete [] p;
// });
//C++11的包装器类模板:function
//包装器的使用。
function<void ()> f = showInfo;
f();
return 0;
}
对象树
在我以往文章中介绍过智能指针,可以实现栈区对象托管堆区资源,现在我们在介绍一种解决办法:对象树。
就是当一个基类有很多代派生类时,或者有很多派生类时我们可以将每次开辟的子类资源都托管给基类对象,基类内部维护一个链表,表成员是基类指针,因为继承父类指针指向自类指针天然且安全。这样的好处与智能指针有异曲同工之妙,大家可以根据自己的项目需求来选择合适的方法来托管堆区资源。
#include <iostream>
#include <list>
using namespace std;
class Object;
typedef list<Object*> childrenList;
class Object
{
public:
//老祖宗类中的孩子列表
childrenList children;
public:
Object(Object* parent = nullptr)
{
if(parent != nullptr)
{
parent->children.push_back(this);
}
}
virtual ~Object()
{
for(auto it = children.begin(); it != children.end(); it++)
{
delete *it;
}
}
};
//所有的类全部都继承自QObject类
class A : public Object
{
public:
A(Object* parent = nullptr)
{
if(parent != nullptr)
{
parent->children.push_back(this);
}
cout << "A的构造" << endl;
}
~A()
{
cout << "A的析构" << endl;
}
};
//所有的类继承自于Object类
class B : public Object
{
public:
B(Object* parent = nullptr)
{
if(parent != nullptr)
{
parent->children.push_back(this);
}
cout << "B的构造" << endl;
}
~B()
{
cout << "B的析构" << endl;
}
};
int main()
{
// Object obj;
// A* a = new A(&obj);
//这也是为什么Qt的main.cpp中的对象是栈上对象的原因。
B b;
A* a1 = new A(&b);
return 0;
}
后续的STL的使用,我会陆续发出,敬请关注。
观察者设计模式:
1.引言
观察者设计模式:就用来解决两个不相关对象之间的一对一或一对多的通信模型。他是一种编程套路
2.什么是观察者设计模式?
观察者模式是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。在观察者模式中,主体是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知。观察者模式不仅被广泛应用于软件界面元素之间的交互,在业务对象之间的交互、权限管理等方面也有广泛的应用。 [1]
观察者模式(Observer)完美的将观察者和被观察的对象分离开。观察者模式在模块之间划定了清晰的界限,提高了应用程序的可维护性和重用。
3.观察者设计模式是来解决什么问题?
观察者设计模式定义了对象间的一种一对多的组合关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。
观察者和被观察者之间存在“观察”的逻辑关联,当被观察者发生改变的时候,观察者就会观察到这样的变化,并且做出相应的响应。
4.观察者设计模式的简易编程套路是什么?
1.假定两个类,一个为观察者类,一个为被观察者类。
2.观察者类中,定义一个对某个事件感兴趣的处理函数,一般也叫槽函数。
3.被观察者类中,定义一个数据结构,用来保存观察者对哪一个事件id(信号)感兴趣,使用数据结构建立信号与对象之间的映射关系。
4.被观察者类中,定义两个方法函数:
一个方法为:添加观察者与其感兴趣的事件id(信号)加入到容器之中。
另一个方法为:信号函数:通知事件函数执行逻辑:首先遍历容器之中,有没有感兴趣的事件ID,如果有,则代表一系列的观察者,对这个事件感兴趣,那么再次遍历观察者列表,让每一个观察者执行相应的槽函数。
#include <iostream>
#include<map>
#include<list>
using namespace std;
class Bace_recv{
public:
virtual void slot_function(int msgid)=0;
virtual ~Bace_recv()=default;
};
class recv :public Bace_recv{
public:
void slot_function(int msgid) override{
switch (msgid) {
case 1:
cout << "接收到信号1,并执行了信号1所要执行的逻辑" << endl;
break;
case 2:
cout << "接收到信号2,并执行了信号2所要执行的逻辑" << endl;
break;
case 3:
cout << "接收到信号3,并执行了信号3所要执行的逻辑" << endl;
break;
}
}
};
class sender{
private:
//可以将value换成链表 将观察者设计为多个对象多个行为对多个信号
map<int,Bace_recv*> mp;
public:
void addsendMap(int msgid,Bace_recv* recv){
mp[msgid]=recv;
}
void signal(int msgid,int num){
cout<<msgid<<"号对象";
mp[msgid]->slot_function(num);
}
};
int main()
{
Bace_recv *rec1=new recv;
Bace_recv *rec2=new recv;
Bace_recv *rec3=new recv;
sender sd;
sd.addsendMap(2,rec1);
sd.addsendMap(3,rec2);
sd.addsendMap(1,rec3);
sd.signal(1,2);
sd.signal(2,3);
sd.signal(3,1);
return 0;
}