• 0722-----C++Primer听课笔记----------句柄类和智能指针


    1.再说智能指针

      1.1  为什么要用智能指针?对于一个指针,它指向一个动态分配内存的对象,若同时有多个指针指向该对象,那么当我们delete的时候,就会出现delete 一个无效内存的错误,因为该对象已经被delete过了,所以这就造成了错误。针对这一情况,我们想到,new 和 delete 必须是成对出现的,那么联想到类里面,很容易想到这个构造函数和析构函数也是成对出现的,对于每一个对象,初始化的时候会调用构造函数,而销毁的时候必然要调用析构函数。因此我们就可以对 指针 进行封装,将该指针的初始化,即资源的获取放在构造函数里,将资源的销毁放在析构函数里,把该类的对象看做一个指针,行使指针的功能,并且不会出现内存泄露的问题。如下例所示:

    #ifndef __SMARTPTR_H__
    #define __SMARTPTR_H__
    
    #include <iostream>
    
    class Animal{
        public:
            Animal(){
                std::cout << "Animal..." << std::endl;
            }
    
            ~Animal(){
                std::cout << "~Animal..." << std::endl;
            }
    
            void display(){
                std::cout << "in Animal..." << std::endl;
            }
    };
    
    class SmartPtr{
        public:
            SmartPtr();
            explicit SmartPtr(Animal *ptr);
            ~SmartPtr();
    
            void reset_ptr(Animal *ptr);
            const Animal *get_ptr() const;
    
            Animal *operator->();
            const Animal *operator->() const;
    
            Animal &operator*();
            const Animal &operator*() const;
    
            operator bool() const;
    
        private:
            SmartPtr(const SmartPtr &other);
            SmartPtr &operator=(const SmartPtr &other);
            Animal *ptr_;
    };
    #endif
    
    #include "smartptr.h"
    
    SmartPtr::SmartPtr()
        :ptr_(NULL)
    {
    }
    
    SmartPtr::SmartPtr(Animal *ptr)
        :ptr_(ptr)
    {
    }
    
    SmartPtr::~SmartPtr(){
        delete ptr_;
    }
    
    void SmartPtr::reset_ptr(Animal *ptr){
        if(ptr_ != ptr){
            delete ptr_;
            ptr_ = ptr;
        }
    }
    
    const Animal *SmartPtr::get_ptr() const{
        return ptr_;
    }
    
    Animal *SmartPtr::operator->(){
        return ptr_;
    }
    
    const Animal *SmartPtr::operator->() const{
        return ptr_;
    }
    
    Animal &SmartPtr::operator*(){
        return *ptr_;
    }
    
    
    const Animal &SmartPtr::operator*() const{
        return *ptr_;
    }
    
    SmartPtr::operator bool()const{ //这里进行了类型转换
        return ptr_;        // 把ptr 转化成了bool类型
    }
    
    
    
    
    #include "smartptr.h"
    #include <iostream>
    
    using namespace std;
    
    int main(int argc, const char *argv[])
    {
        SmartPtr smart(new Animal);
        smart->display();
    
        (*smart).display();
    
        smart.reset_ptr(NULL);
        if(!smart){ //类中将该对象转换成了bool型
            cout << "ptr == NULL" << endl;
        }
    
        smart.reset_ptr(new Animal);
        if(smart){
            cout << "ptr != NULL" << endl;
        }
        return 0;
    }
    

      1.2 关于箭头操作符(->)

        1.2.1 箭头操作符可以理解为一个递归调用的过程,直到返回结果为指针时才停止递归。这里以c++primer中的例子来说明问题。假如有一个类的对象 point,我们在主函数中调用 point->action(),由于优先级规则,这里相当于调用(point->action)();换句话说,我们想要调用的是point->action 的求值结果,这里编译器这样求值:

          a)如果 point是个指针,则编译器将代码编译为调用该对象的action 成员;

          b)如果 point 是定义了 operator->操作符重载的一个类的对象,则 point ->action 相当于 调用 (point.operator->) ->action, 求出括号内结果后重复一二步。

        1.2.2 举例来说明这个问题,注意,重载箭头的返回值必须是指向类类型的指针,或者定义了自己的重载箭头操作符的类类型对象。

    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    
    class A{
        public:
            void action(){
                cout << "Action in class A" << endl;
            }
    };
    
    class B{
        public:
            A *operator->(){
                return &a_;
            }
    
            void action(){
                cout << "Action in class B" << endl;
            }
        private:
            A a_;
    
    };
    
    class C{
        public:
            B &operator->(){
                return b_;
            }
    
            void action(){
                cout << "Action in class C" << endl;
            }
        private:
            B b_;
    };
    
    int main(int argc, const char *argv[])
    {
        C *pc = new C;
        pc->action();
    
        C c;
        c->action();
    
        return 0;
    }
    

      

    2.句柄类

      2.1 为什么要用句柄类?句柄类是什么?我们试图把一个继承体系中的多个对象放入一个数组中(即多个不同派生类的对象都放到一个数组中)。如果采用指针,会造成内存管理的极度混乱(因为有些对象可能已经被销毁,那么该指针就指向一块无用的内存)。在这个例子中,我们把 Animal 指针封装在一个Handle 类中,这个Handle 类实现的是深拷贝(拷贝构造函数里调用 Animal 类的copy 函数,将对象的全部内容拷贝,生成一个新的对象),这样每个Handle 对象相互独立。

        为了实现Handle的深拷贝,我们在Animal中添加copy虚函数,这样就可以通过Animal* 达到赋值实际对象的目的(Cat、Dog)是一种多态。

        为了实现通过Handle可以控制Animal,我们为Handle重载了->操作符,使它表现的像一个指针。(还有一种方案,在Handle中实现display,然后通过ptr去调用Animal内部的display)。

        封装句柄的最终目的是在数组、vector中封装Animal系列对象的多态行为。

          目前这个句柄的特点:Animal系列的继承体系对用户是可见的。

        可以改进的地方:把深拷贝改为引用计数。

     

    #ifndef __ANIMAL_H__
    #define __ANIMAL_H__
    
    #include <iostream>
    
    class Animal{
        public:
            virtual ~Animal() {};
            virtual void display() const = 0 ;
            virtual Animal *copy() const = 0 ;
    };
    
    class Cat : public Animal{
        public:
            void display() const{
                std::cout << "Cat ..." << std::endl;
            }
    
            Cat *copy() const{
                std::cout << "Cat copy" << std::endl;
                return new Cat(*this);
            }
    };
    
    class Dog : public Animal{
        public:
            void display() const{
                std::cout << "Dog ..." << std::endl;
            }
    
            Dog *copy()const{
                return new Dog(*this);
            }
    };
    
    #endif
    #ifndef __HANDLE_H__
    #define __HANDLE_H__
    #include "animal.h"
    
    class Handle{
        public:
            Handle();
            Handle(const Animal &ptr);
            Handle(const Handle &other);
            Handle &operator=(const Handle &other);
            ~Handle();
    
            Animal *operator->();
            const Animal *operator->() const;
    
    
        private:
            Animal *ptr_;
    };
    
    
    #endif
    #include "handle.h"
    #include "animal.h"
    #include <iostream>
    Handle::Handle()
        :ptr_(NULL)
    {
    }
    
    Handle::Handle(const Animal &ptr)
        :ptr_(ptr.copy())
    {
        std::cout << "Handle constructor ..." << std::endl;
    }
    
    Handle::Handle(const Handle &other)
        :ptr_(other.ptr_->copy())
    {
        std::cout << "Handle copy cnstructor... " << std::endl;
    }
    
    Handle &Handle::operator=(const Handle &other){
        if(this != &other){
            delete ptr_;
            ptr_ = other.ptr_->copy();
        }
        return *this;
    }
    
    Handle::~Handle(){
        delete ptr_;
    }
    
    Animal *Handle::operator->(){
        return ptr_;
    }
    
    const Animal *Handle::operator->() const{
        return ptr_;
    }
    
    
    #include "handle.h"
    #include "animal.h"
    #include <iostream>
    #include <vector>
    using namespace std;
    
    int main(int argc, const char *argv[])
    {
        vector<Handle> vec;
        Cat c1, c2, c3;
        Dog d1,d2;
    
        vec.push_back(Handle(c1));
        vec.push_back(Handle(c2));
        vec.push_back(Handle(c3));
        vec.push_back(Handle(d1));
        vec.push_back(Handle(d2));
    
        for(vector<Handle>::iterator it = vec.begin(); it != vec.end(); ++it){
           (*it)->display();
        }
    
        Handle h(c1);
        h->display();
    
        return 0;
    }
    
    

       2.2 关于上例中的push_back函数,以Cat c1;为例,从语法的角度理解,这里先用c1对象去初始化一个Handle 对象,这里Handle对象时临时的,它的生存期值是在本行,生成的handle 临时对象会会调用handle类的拷贝构造函数 ,生成一个副本,vec 里面放的就是这个副本。注意在初始化Handle对象的时候,调用有参数的构造函数,在这个构造函数初始化列表中,我们调用了Animal类的copy函数,实现了对象的深拷贝,即将对象的全部内容拷贝到一个新的对象中去,之后二者再无关联。

    3.句柄类和智能指针的主要区别:

      a) 智能指针主要目的在于使用RAII管理资源,实现资源的自动释放。

      b) 句柄类也实现了智能指针的功能,但是其主要目的是为了在数组中实现多态。

      

  • 相关阅读:
    Extjs5.0中的新特性
    Extjs4中的常用组件:Grid、Tree和Form
    Extjs4中的布局
    Extjs4中的store
    [IIS]IIS扫盲(三)
    [IIS]IIS扫盲(二)
    [IIS]IIS扫盲(一)
    [IIS]在CMD中IIS的使用
    检索 COM 类工厂中 CLSID 为 {00024500-0000-0000-C000-000000000046} 的组件时失败
    [SQL]向3个表插入数据的存储过程 和 C# 代码
  • 原文地址:https://www.cnblogs.com/monicalee/p/3861336.html
Copyright © 2020-2023  润新知