Snowming04's Blog
一颗红❤
Toggle navigation
Snowming04's Blog
主页
Cobalt Strike
Accelerated C++
区块链安全
友链
关于我
常用工具
代码积累
归档
标签
【Accelerated C++】课时14:近乎自动地管理内存
? C++ ?
2020-04-16 14:08:06
663
0
0
snowming
? C++ ?
在十三章中写的`Student_info`句柄类,不仅提供了对一个学生记录进行操作的接口,还可以用来操纵一个指向实际实现对象的指针。我们想把此类拆成两种独立的类: 1. 接口类:对学生记录进行操作 2. 类指针类:这个类像一个指针一样工作,不过它是用来管理底层的内存的。 这样拆分之后,可以把一个类指针类和几个接口类放在一起使用。  # 0x01 用来复制对象的句柄 ## 指针引发的问题  ## 一个通用句柄 ``` template <class T> class Handle { public: Handle():p(0){} //初始化为零指针 Handle(const Handle& s):p(0){if(s.p) p=s.p->clone();} Handle& operator=(const Handle&); ~Handle(T *t):p(t){} operator bool() const{return p;} T* operator*() const; T* operator->() const; private: T* p; }; ``` ``` template<class T> Handle<T>& Handle<T>::operator=(const Handle& rhs) { if(&rhs!=this) { delete p; p=rhs.p?rhs.p->clone():0; } return *this; } ```   ``` template<class T> T& Handle<T>::operator*() const { if(p) //在类里面定义的,如果p不是零指针 return *p; throw runtime_error("unbound Handle"); } template<class T> T* Handle<T>::operator->() const { if(p) return p; throw runtime_error("unbound Handle"); } ``` 定义了自己的`->`运算符和`*`运算符。 - `*` 运算符:`operator*` 实际上是得到一个引用,<u>注意引用而非对象</u>,这样我们才能调用虚拟函数。所以如果运行 `(*student).grade()`,那么我们是在通过一个引用调用 grade 函数,在运行的时候系统才会决定调用哪一个版本的 grade 函数。 - `->` 运算符:`->`函数实际上返回对象封装的指针,通过指针也可以获得动态绑定特性。所以假如我们运行`student->grade()`,实际上是通过 student 对象里的 p 指针来调用 grade 函数,在运行的时候系统会根据 p 所指对象的实际类型决定实际调用的是哪一个版本的 grade 函数。 ## 使用一个通用句柄 使用 Handle 类重写基于指针的成绩程序: ``` int main() { vector<Handle<Core>> students; //改变其类型 Handle<Core> record; //改变其类型 char ch; string::size_type maxlen=0; //读取并存储数据 while(cin>>ch) { if(ch=='U') record=new Core; //为一个Core类型对象分配内存 else record=new Grad; //先调用Handle<T>::->,然后调用虚拟的read函数 record->read(cin); maxlen=max(maxlen,record->name().size()); students.push_back(record); } //重写compare函数使之可以用const Handle<Core>&类型做参数 sort(students.begin(),students.end(),compare_Core_handles); //输出姓名与成绩 for(vector<Handle<Core>>::size_type i=0; i != students.size();++i) { //students[i]是一个Handle类型对象,对其间接引用以调用函数 cout<<setw(maxlen+1)<<students[i]->name(); try { double final_grade=students[i]->grade(); streamsize prec=cout.precision(); cout<<setprecision(3)<<final_grade<<setprecision(prec)<<endl; } catch(domain_error) { cout<<e.what()<<endl; } //没有delete语句 } return 0; } ``` >一个问题:为什么没有 delete 语句? 答:因为 Handle 类会替我们管理内存,所以我们不需要另外调用 delete 函数来删除 students 中的指针元素所指向的对象。 重新编写 Student_info 类使其成为一个纯接口类,让它从管理指针的工作中解脱出来: ``` class Student_info { public: student_info(){} student_info(std::istream& is){read(is);} //现在不再需要复制构造函数,也不再需要赋值操作和析构函数了 std::istream& read(std::istream&); std::string name() const { if(cp) return cp->name(); else throw runtime_error("uninitialized Student"); } double grade() const { if(cp) return cp->grade(); else throw runtime_error("uninitialized Student"); } static bool compare(const Student_info& s1,const Student_info& s2) { return s1.name()<s2.name(); } private: Handle<Core> cp; }; ``` 由于 Handle 类可以很好地管理它所操纵的底层对象,因此我们不需要再去实现那些用于控制复制的函数。 重写 read 函数: ``` istream& Student_info::read(istream& is) { char ch; is >> ch; //获取记录类型 //为适当的类型分配新的对象 //使用Handle<T>(T*)来为指向那个对象的指针构造一个Handle<Core>的对象 //调用Handle<T>::operator来将Handle<Core>赋值给左边的对象 if(ch=='U') cp = new Core(is); else cp = new Grad(is); return is; } ```  # 0x02 引用计数句柄 需求: 现在在复制 Handle 类(也就是指针)的时候总是会赋值 Handle 类型对象指向的对象。我们希望有种 Handle 类,只复制指针,不复制底层对象。也就是我们允许几个 Handle 类型对象指向同一个底层对象。但是这种情况下,要还是需要在某个时候释放这个被指向的底层对象占用的内存。显然,释放对象内存的时机应该选在指向该对象的最后一个 Handle 类型对象被删除的时候。 还有一些时候,一旦类的对象生成以后,其他类没有办法改变对象的状态。在这种时候,没有理由要对基层的对象进行复制,复制只会浪费 CPU 时间与内存空间。 所以我们想要定义一种 Handle 类,它在 Handle 类型对象(也就是指针)本身复制的时候不对底层的对象进行实际的复制。 ## 引用计数  ## Ref_handle 类  ``` template <class T> class Ref_handle { public: //像管理指针一样管理引用计数 Ref_handle():refptr(new size_t(1)),p(0){} Ref_handle(T* t):refptr(new size_t(1)),p(t){} Ref_handle(const Ref handle& h):refptr(h.refptr),p(h.p) { ++*refptr; } Ref_handle& operator=(const Ref_handle&); ~Ref_handle(); //同前 operator bool() const {return p;} T& operator*() const { if(p) return *p; throw std::runtime_error("unbound Ref_handle"); } T& operator->() const { if(p) return p; throw std::runtime_error("unbound Ref_handle"); } private: T* p; size_t* refptr; }; ``` ## 赋值运算符函数 在赋值运算符函数中也使用了引用计数,而不是直接复制底层对象: ``` template<class T> Ref_handle<T>& Ref_handle<T>::operator=(const Ref_handle& rhs) { ++*rhs.refptr; //释放左操作数对象,如果必要的话删除指针 if(--*refptr == 0) { delete refptr; delete p; } //复制右操作数对象的值 refptr=rhs.refptr; p=rhs.p; return this; } ```  ## 析构函数 析构函数和赋值算符函数一样,先检查要删除的 Ref_handle 对象是否指向 T 对象的最后一个引用。如果是的话,就删除指针所指向的对象并释放其占用的内存: ``` template<class T> Ref_handle<T>::~Ref_handle() { if(--*refptr==0) { delete refptr; delete p; } } ``` ## 新的问题 Handle 类在复制句柄(也就是指针)的时候总会复制其指向的底层对象。但是 Ref_handle 类无论需要与否,它都不会对对象进行复制。  # 0x03 可以让你决定什么时候共享数据的句柄 ``` template <class T> class Ptr { public: //新加一个成员函数,用来在需要的时候有条件地复制对象 void make_unique() { //refptr是引用计数指针 if(*refptr != 1) { --*refptr; refptr = new size_t(1); p=p?p->clone():0; } } //剩下的部分除了名字以外与Ref_handle类相同 Ptr():refptr(new size_t(1)),p(0){} Ptr(T* t):refptr(new size_t(1)),p(t){} Ptr(const Ptr& h):refptr(h.refptr),p(h.p){++*refptr;} Ptr& operator=(const Ptr&); ~Ptr(); operator bool() const {return p;} T& operator*() const; //返回一个引用 T* operator-> const; //返回一个指针 private: T* p; size_t* refptr; }; ``` regard函数: ``` void Student_info::regard(double final, double thesis) { //在改变对象之前先得到自己的复件 cp.make_unique(); if(cp) cp->regard(final,thesis); else throw runtime_error("regrade of unknow student"); } ``` ## 模板特化    ## 复制在什么时候是必要的? 
上一篇:
【Accelerated C++】课时15:再探字符图形
下一篇:
【Accelerated C++】课时13:使用继承与动态绑定
0
赞
663 人读过
新浪微博
微信
腾讯微博
QQ空间
人人网
提交评论
立即登录
, 发表评论.
没有帐号?
立即注册
0
条评论
More...
文档导航
没有帐号? 立即注册