c++智能指针和对象树、观察者模式

智能指针

智能指针并非指针,他是一个栈上的类对象。这个对象托管了堆区的资源。
在这里插入图片描述
智能指针给我带来的意义是什么及背后的机制:
智能指针所使用的机制是什么呢?
当栈上的智能指针对象出栈时,所托管的堆区对象自动进行析构。
用到的知识点就是栈上对象出栈自动销毁的特性,因为出栈即自动调用析构。
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;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 我可以帮助您写一个使用 C 语言实现的观察者模式案例。首先,在 C 语言中,需要定义一个抽象观察者类,用来定义观察者的操作,比如更新状态等。之后,定义具体的观察者类,实现观察者抽象类的抽象方法。然后,定义主题(被观察者)类,定义观察者的注册、移除和通知方法。最后,在 main 函数中创建一个主题实例和一些观察者实例,并将观察者注册到主题实例中,然后发布一些消息,观察者就会收到更新消息。 ### 回答2: 观察者模式是软件设计中常用的一种模式,用于实现对象间的松耦合关系。在观察者模式中,一个被观察者对象(也称为主题)维护了一个观察者列表,并在自身状态发生改变时通知观察者对象。观察者对象可以根据被观察者的通知来进行相应的更新操作。 以下是一个使用C语言实现观察者模式的简单案例: ```c #include <stdio.h> // 定义观察者接口 typedef struct Observer { void (*update)(struct Observer*); // 观察者更新函数 } Observer; // 定义被观察者接口 typedef struct Observable { Observer* observers[10]; // 最多支持10个观察者 int count; // 实际观察者数量 void (*notify)(struct Observable*); // 通知观察者函数 void (*addObserver)(struct Observable*, Observer*); // 添加观察者函数 } Observable; // 触发观察者更新操作 void notify(struct Observable* observable) { for (int i = 0; i < observable->count; i++) { observable->observers[i]->update(observable->observers[i]); } } // 添加一个观察者 void addObserver(struct Observable* observable, Observer* observer) { if (observable->count < 10) { observable->observers[observable->count] = observer; observable->count++; } } // 定义具体观察者类 typedef struct ConcreteObserver { Observer observer; void (*update)(Observer*); // 重写观察者更新函数 } ConcreteObserver; void update(Observer* observer) { printf("观察者被通知并更新\n"); } // 初始化具体观察者对象 ConcreteObserver* createObserver() { ConcreteObserver* observer = malloc(sizeof(ConcreteObserver)); observer->observer.update = update; return observer; } int main() { // 初始化被观察者对象 Observable* observable = malloc(sizeof(Observable)); observable->count = 0; observable->notify = notify; observable->addObserver = addObserver; // 初始化观察者对象 ConcreteObserver* observer = createObserver(); // 添加观察者 observable->addObserver(observable, &(observer->observer)); // 被观察者状态改变,触发通知 observable->notify(observable); return 0; } ``` 在这个案例中,我们定义了一个`Observable`结构体作为被观察者对象,其中包含了观察者列表和相关的函数指针。同时,我们还定义了一个`Observer`结构体作为观察者接口,以及一个`ConcreteObserver`结构体作为具体观察者类。在`main`函数中,我们创建了被观察者对象和一个观察者对象,并将观察者对象添加到被观察者对象中,然后触发被观察者状态改变,最终触发观察者的更新操作。 ### 回答3: 观察者模式是一种行为设计模式,用于定义对象之间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。以下是一个用C语言实现的观察者模式案例。 首先,我们定义两个观察者类和一个被观察者类。观察者类包含一个update方法,被观察者类包含一个attach方法添加观察者,一个detach方法移除观察者,以及一个notify方法通知观察者。 ```c #include <stdio.h> // 观察者接口 typedef struct Observer { void (*update)(struct Observer*); // 更新方法 } Observer; // 被观察者接口 typedef struct Observable { Observer** observers; // 观察者数组 int count; // 观察者数量 void (*attach)(struct Observable*, Observer*); // 添加观察者 void (*detach)(struct Observable*, Observer*); // 移除观察者 void (*notify)(struct Observable*); // 通知观察者 } Observable; // 观察者更新方法的具体实现 void update(Observer* self) { printf("观察者被通知并更新\n"); } // 添加观察者 void attach(Observable* self, Observer* observer) { self->observers[self->count++] = observer; } // 移除观察者 void detach(Observable* self, Observer* observer) { for (int i = 0; i < self->count; i++) { if (self->observers[i] == observer) { for (int j = i; j < self->count - 1; j++) { self->observers[j] = self->observers[j + 1]; } self->count--; break; } } } // 通知观察者 void notify(Observable* self) { for (int i = 0; i < self->count; i++) { self->observers[i]->update(self->observers[i]); } } int main() { // 创建观察者和被观察者实例 Observer observer1 = { &update }; Observer observer2 = { &update }; Observable observable; // 设置被观察者的观察者数组 observable.observers = malloc(sizeof(Observer*) * 2); observable.count = 0; // 添加观察者到被观察者 observable.attach(&observable, &observer1); observable.attach(&observable, &observer2); // 通知观察者 observable.notify(&observable); // 移除观察者并释放内存 observable.detach(&observable, &observer1); observable.detach(&observable, &observer2); free(observable.observers); return 0; } ``` 以上是一个简单的观察者模式的C语言实例。这个案例中,观察者类和被观察者类之间通过指针实现了依赖关系。当被观察者调用notify方法时,观察者会收到通知并调用update方法进行更新。你可以根据实际需求进行修改和扩展,例如添加更多的观察者和被观察者。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值