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