首页 > 代码库 > 0722-----C++Primer听课笔记----------句柄类和智能指针

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