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) 句柄类也实现了智能指针的功能,但是其主要目的是为了在数组中实现多态。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。