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) 句柄类也实现了智能指针的功能,但是其主要目的是为了在数组中实现多态。

 

 

  

0722-----C++Primer听课笔记----------句柄类和智能指针,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。