c++中代理类的学习
https://blog.csdn.net/lcg910978041/article/details/51468680
C++代理类是为了解决这样的问题: 容器通常只能包含一种类型的对象,所以很难在容器中存储对象本身。
怎样设计一个c++容器,使它有能力包含类型不同而彼此相关的对象?
代理运行起来和他所代表的对象基本相同,但是允许将整个派生层次压缩在一个对象类型中。
代理类的每个对象都代表另一个对象,该对象可以是位于一个完整继承层次中的任何类的对象。通过在容器中使用代理对象而不是对象本身的方式,就是代理类的精髓思想所在。
1.为什么c++需要代理类
考虑如下的一个小实例:假设有一个类,命名为RoadVehicle,代表陆地上的车辆,简单的定义如下:
- //定义陆地上的车辆
- class RoadVehicle
- {
- public:
- RoadVehicle(){} //默认构造函数,容许声明RoadVehicle的数组
- double get_weight()const
- {
- return 0.0;
- }
- };
现在我们需要用一个容器或者数组来保存这个类的一些列对象,那么我们可以如下声明数组来存放这些对象:
- RoadVehicle parking_lot[100];
这样做会造成一个很明显的问题:当我们有另一类车辆 (比如飞机类AirCraft) 时,我们将RoadVehicle类和AirCraft类继承自同一个父类Vehicle,有如下的结构:
- class Vehicle
- {
- public:
- virtual double get_weight()const = 0;
- };
- //定义陆地上的车辆
- class RoadVehicle:public Vehicle
- {
- public:
- RoadVehicle(){} //默认构造函数,容许声明RoadVehicle的数组
- double get_weight()const
- {
- return 0.0;
- }
- };
- //定义飞机
- class AirCraft:public Vehicle
- {
- public:
- AirCraft(){} //默认构造函数,容许声明AirCraft的数组
- double get_weight()const
- {
- return 1.0;
- }
- };
当有如上继承结构出现时,我们再来考虑如何用一个容器或者数组存放这些对象,尽管可以声明多个数组来存放,如下:
- RoadVehicle parking_lot1[100];
- AirCraft parking_lot2[100];
但是当我们的车辆的类型越来越多,比如还有AutoVehicle类,Helicopter类等等时,这种方式需要声明对应的数组来存放,很显然这不符合c++的精神。
既然RoadVehicle类和AirCraft类均继承自Vehicle类,那我们声明如下的数组来存放这些对象,又会发生什么事情呢?
- Vehicle parking_lot[100];
但是这又会带来两个问题:
第一:Vehicle是一个抽象类,它拥有一个纯虚函数,它不能生成对象,因此他无法定义这样的数组;
第二:即使我们在vehicle中将纯虚函数修改为虚函数,使它可以生成对象,也会出现问题,比如有如下代码:
- RoadVehicle x;
- parking_lot[num] = x;
此时,会把x转换成一个Vehicle对象,同时会丢失所有在Vehicle类中没有的成员,然后将剪裁了的对象复制到parking_lot数组中去。
解决上述两个问题的方法非常简单,即存储Vehicle对象的指针:
- Vehicle * parking_lot[100];
此时,上述操作变为:
- RoadVehicle x;
- parking_lot[num] = &x;
但是,这种方法又带来了新的问题:
由于x是局部变量,当x超出其作用域时,parking_lot数组中的指针变成了野指针。这一问题可以如下解决:
- RoadVehicle x;
- parking_lot[num] = new RoadVehicle(x); //即:使parking_lot中的指针指向x的一个副本
但上述方法实施的前提是我们明确的知道了要放入parking_lot中的对象的静态类型,在本例中,我们知道要放入parking_lot的是RoadVehicle,而不是AirCraft或其他的类对象。
当我们不知道要放入parking_lot中的对象的静态类型时,比如,我们我们想让parking_lot[p]指向一个新建立的Vehicle,并且这个Vehicle和parking_lot[q]中的对象相同,这时我们并不知道parking_lot[q]中指针所指的对象的静态类型,那我们该如何复制parking_lot[q]所指向的副本给parking_lot[p]呢?
显然:
- if(p != q)
- {
- delete parking_lot[p];
- parking_lot[p] = parking_lot[q];
- }
这是不行的,这样会导致parking_lot[p]和parking_lot[q]指向同一个对象
- if(p != q)
- {
- delete parking_lot[p];
- parking_lot[p] = new Vehicle(parking_lot[q]); (这样的构造 是不对的 1 Vehicle无法产生对象 2 即使产生也不一定对 几乎可能会被裁剪掉 )
- }
这也是不行的,因为Vehicle无法产生对象,即使能产生,也是被剪裁了的,是Vehicle对象, 而不是parking_lot[q]所指的对象的类型。 (因为具体指向的类型 我们不清楚)
事实上,这里的根本原因在于,我们无法知道parking_lot[q]所指对象的静态类型,c++中解决这一类问题是用多态。我们需要一个克隆函数,当parking_lot[q]调用自己的克隆函数时,复制自己的一个副本,并返回一个该副本的指针。因此我们的代码变为如下:
- #include <iostream>
- using namespace std;
- class Vehicle
- {
- public:
- virtual double get_weight()const = 0;
- virtual Vehicle* copy() const = 0;
- };
- //定义陆地上的车辆
- class RoadVehicle:public Vehicle
- {
- public:
- RoadVehicle(){} //默认构造函数,容许声明RoadVehicle的数组
- RoadVehicle(const RoadVehicle& RV){}
- double get_weight()const
- {
- return 0.0;
- }
- Vehicle* copy()const
- {
- return new RoadVehicle(*this);
- }
- };
- //定义飞机
- class AirCraft:public Vehicle
- {
- public:
- AirCraft(){} //默认构造函数,容许声明AirCraft的数组
- AirCraft(const AirCraft& AC){}
- double get_weight()const
- {
- return 0.0;
- }
- Vehicle* copy()const
- {
- return new AirCraft(*this);
- }
- };
由于parking_lot数组中存放的都是Vehicle类型的指针,而其所实际指向的对象是Vehicle的任意子类,因此,在释放parking_lot中指针所指向的内存时,我们需要虚析构函数,否则,会造成内存泄露,因此我们为每个类添加虚析构函数。代码如下:
- #include <iostream>
- using namespace std;
- class Vehicle
- {
- public:
- virtual double get_weight()const = 0;
- virtual Vehicle* copy() const = 0;
- virtual ~Vehicle(){}
- };
- //定义陆地上的车辆
- class RoadVehicle:public Vehicle
- {
- public:
- RoadVehicle(){} //默认构造函数,容许声明RoadVehicle的数组
- RoadVehicle(const RoadVehicle& RV){}
- double get_weight()const
- {
- return 0.0;
- }
- Vehicle* copy()const
- {
- return new RoadVehicle(*this);
- }
- ~RoadVehicle(){}
- };
- //定义飞机
- class AirCraft:public Vehicle
- {
- public:
- AirCraft(){} //默认构造函数,容许声明AirCraft的数组
- AirCraft(const AirCraft& AC){}
- double get_weight()const
- {
- return 0.0;
- }
- Vehicle* copy()const
- {
- return new AirCraft(*this);
- }
- ~AirCraft(){}
- };
如此一来,上面存在的问题,可以如下解决:
- if(p != q)
- {
- delete parking_lot[p];
- parking_lot[p] = parking_lot[q]->copy(); //这一点 很重要!!!!!!!!!!!! 别忘记 这一步骤 delete parking_lot[p];
- }
上面的方法,在一定程度上解决了我们的问题,但是它在parking_lot数组中存储的是指针,这使得用户显示的处理内存分配,那么有没有一种方法既能够不显示的处理内存分配,又能够保持类Vehicle在运行时动态绑定呢?这就是我们的代理类了。
=======================================================================================================
我们为Vehicle类创建一个代理类,假设命名为VehicleSurrogate,每个VehicleSurrogate对象代表一个Vehicle对象,只要VehicleSurrogate对象存在,那么它所关联的Vehicle对象就存在,复制VehicleSurrogate对象就会复制相对应的Vehicle对象,给代理赋新值也会先删除旧的关联的对象,再复制新的对象。
我们可以如下定义代理类:
- class VehicleSurrogate
- {
- public:
- VehicleSurrogate():vp(0){} //默认构造函数,使得可以声明VehicleSurrogate的数组
- VehicleSurrogate(const VehicleSurrogate& VS)//以自身对象初始化 {
- vp = VS.vp ? (VS.vp->copy()) : (0);
- }
- VehicleSurrogate(const Vehicle& V):vp(V.copy()){} //以它所代理的Vehicle对象初始化 这两个就是 定义了不同掉copy构造函数
- VehicleSurrogate& operator=(const VehicleSurrogate& VS)//重载赋值 {
- if(this != &VS) { //看看是不是自己 防止自我复制
- delete this->vp;
- this->vp = VS.vp?VS.vp->copy():0;
- }
- return *this;
- }
- ~VehicleSurrogate() {
- delete vp;
- }
- //代理Vehicle类行为的函数,Vehicle有多少个行为函数,这里就需要重新定义多少个代理类函数
- double get_weight()const {
- return vp->get_weight();
- }
- private:
- Vehicle* vp;
- };
有了上述代理类之后,我们就可以如下操作:
- VehicleSurrogate parking_lot[100]; !!!数组的定义会调用 num 次的默认构造函数
- RoadVehicle x;
- parking_lot[0] = x; 注意:1先调用copy构造函数构造临时对象 上面两个copy选择其中一个 2再调用代理类的 operator = 函数(因为VehicleSurrogate 前面定义数组时候已经定义且默认初始化了 所以调用 = 函数 )
- cout << parking_lot[0].get_weight();
至此,我们利用代理类,就可以即不用显示进行内存分配管理,又可以使得Vehicle子类对象进行动态绑定。
========================================================================!!!!!!!!!!!!!!!!!
2. 智能指针
“c++代理类(一)”中完成的简单代理类虽然解决了最急迫的问题,但效率上又存在了另外的问题,该简单类存在的问题主要是:
每个代理类对象都唯一关联一个实际对象,代理类对象存在则实际对象存在,代理类对象释放则实际类对象也要释放,且复制代理类对象就必须要复制实际类对象。
这在实际类很大的时候复制开销是非常大的。而且,代理类的复制会频繁的发生,比如:作为函数的参数进行值传递,或者作为函数的返回值等等。
我们将对该简单代理类进行改进,改进的思想主要是:在代理类中为其代理的实际对象添加一个标记,该标记指出有多少个代理类对象代理了这个实际对象,这样当我们复制代理类对象时,其实际所代理的对象就不需要复制了,只需要修改该标记即可。
其具体做法如下:
- class VehicleSurrogate
- {
- public:
- ......//跟简单类一样,唯一不同的是需要加入处理标记num的部分
- private:
- Vehicle* vp;
- int * num; //添加的标记字段
- };
有了如上的结构,每次复制代理类时,只需要将(*num)++就可以了。其意义为:绑定到实际对象的代理类又多了一个
上述的策略在不需要修改实际对象时非常有用,即所有代理类对象只是读它所代理的实际对象时,但当某个代理类需要修改它所代理的实际对象时,问题就发生了,由于所有代理类对象实际所代理的对象在内存中是同一份,因此,一个代理类对象所做的修改将会影响其他代理类,因此,此时需要对所代理的实际对象进行复制,且该复制是无法避免的。我们称之为——写时复制。
实现指针的类代码如下:
- #include <iostream>
- using namespace std;
- class Vehicle
- {
- public:
- Vehicle():weight(0.0){} //默认构造函数
- Vehicle(double w):weight(w){} //含参构造函数
- virtual ~Vehicle(){} //虚析构函数必须存在,所有子类对象在析构时都是以Vehicle*的方式调用析构函数的,以虚析构函数调用才能调用到正确的析构函数 才不会导致内存泄露
- virtual Vehicle* copy()const //复制自己 {
- return new Vehicle(*this);
- }
- //读
- double get_weight()const {
- return weight;
- }
- //写
- Vehicle* set_weight(double w) {
- weight = w;
- return this;
- }
- private:
- double weight;
- };
- //定义陆地上的车辆
- class RoadVehicle:public Vehicle
- {
- public:
- RoadVehicle(){} //默认构造函数,容许声明RoadVehicle的数组
- RoadVehicle(double w):Vehicle(w){}
- RoadVehicle(const RoadVehicle& RV):Vehicle(RV.get_weight()){} //拷贝构造函数
- Vehicle* copy()const //复制自己 {
- return new RoadVehicle(*this);
- }
- ~RoadVehicle(){}
- };
- //定义飞机
- class AirCraft:public Vehicle
- {
- public:
- AirCraft(){} //默认构造函数,容许声明AirCraft的数组
- AirCraft(double w):Vehicle(w){}
- AirCraft(const AirCraft& AC):Vehicle(AC.get_weight()){} //拷贝构造函数
- Vehicle* copy()const
- {
- return new AirCraft(*this);
- }
- ~AirCraft(){}
- };
- //智能指针类定义
- class VehicleSurrogate
- {
- public:
- VehicleSurrogate():vp(new Vehicle()),num(new int(1)){} //默认构造函数,使得可以声明VehicleSurrogate的数组
- VehicleSurrogate(const VehicleSurrogate& VS):vp(VS.vp),num(VS.num)//拷贝构造函数,可发现此时它所代理的实际对象并未复制
- {
- ++(*num);
- }
- VehicleSurrogate(const Vehicle& V):vp(V.copy()),num(new int(1)){}//以它所代理的Vehicle对象初始化
- VehicleSurrogate& operator=(const VehicleSurrogate& VS)//重载赋值,可发现此时它所代理的实际对象并未复制
- {
- if(this != &VS)
- {
- //删除原来的旧的关联对象
- if(--(*num) == 0 )
- {
- delete vp;
- delete num;
- }
- //赋值新的关联对象
- vp = VS.vp;
- num = VS.num;
- ++(*num);
- }
- return *this;
- }
- ~VehicleSurrogate()
- {
- if(--(*num)== 0)
- {
- delete vp;
- delete num;
- }
- }
- int get_num()const
- {
- return *num;
- }
- //代理Vehicle类行为的函数,读操作无需复制所代理的实际对象
- double get_weight()const
- {
- return vp->get_weight();
- }
- //写时复制策略,写时必须复制所代理的实际对象
- VehicleSurrogate& set_weight(double w)
- {
- if((*num) == 1)
- {
- vp->set_weight(w);
- }
- else
- {
- --(*num);
- vp = vp->copy();//真正的复制发生在这里
- num = new int(1);
- vp->set_weight(w);
- }
- return *this;
- }
- private:
- Vehicle* vp;
- int * num;
- };
- int main()
- {
- //测试上述智能指针
- VehicleSurrogate parking_lot[100];
- RoadVehicle x(10);
- parking_lot[0] = RoadVehicle(x);
- parking_lot[1] = parking_lot[0];
- parking_lot[0].set_weight(5.0);
- cout << parking_lot[0].get_weight()<<endl<<parking_lot[0].get_num()<<endl;
- cout << parking_lot[1].get_weight()<<endl<<parking_lot[1].get_num()<<endl;
- }
使用智能指针,既保留了简单代理类的优点:无需显示管理内存分配,且能实现所代理的实际对象动态绑定,又省略了过多的复制开销。
转载:
http://blog.csdn.net/liqianyuan2009/article/details/15815341
http://blog.csdn.net/liqianyuan2009/article/details/16345121
更多代理参考:
http://blog.csdn.net/wuzhekai1985/article/det
http://www.cnblogs.com/jiese/p/
c++中代理类的学习的更多相关文章
- java中大数类的学习
java中提供了大数类BigInteger和BigDecimal分别表示大整数类和大浮点数类,这两个类都在java.math.*包中,因此每次必须在开头处引用该包. 一.BigInteger构造函数: ...
- Java中Properties类的学习总结
学习目标: 1.认识properties文件,理解其含义,会正确创建properties文件. 2.会使用java.util.Properties类来操作properties文件. 一.认识prope ...
- java swing中Timer类的学习
最近在完成学校课程的java平时作业,要实现一个计时器,包含开始.暂停以及重置三个功能.由于老师规定要用这个timer类,也就去学习了一下,顺便记录一下. 首先呢去查了一下java手册上的东西,发现t ...
- 关于java中的类的学习
设计模式应该牵扯到类的分布排列了,尽管现在我只能这么表达. 下面来自段帅发来的视频课程中的整理: 类与类之间的关系 每天进步一点点 类是java程序中最小组成单位,要理解后才可以更能理解类继承,重载, ...
- C++中代理类和句柄类
指针是 C 与其他语言区别的重要特征之一,在 C++ 中,指针也被广泛运用,我们通过指针实现多态.然而,众所周知,指针的使用必须小心,否则很容易造成内存泄漏 Memory Leak.当我们有几个指针指 ...
- web.xml 中CharacterEncodingFilter类的学习
过滤器配置 当前台JSP页面和JAVA代码中使用了不同的字符集进行编码的时候就会出现表单提交的数据或者上传/下载中文名称文件出现乱码的问题 //编码方式配置 <filter> <fi ...
- 重学 Java 设计模式:实战代理模式「模拟mybatis-spring中定义DAO接口,使用代理类方式操作数据库原理实现场景」
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 难以跨越的瓶颈期,把你拿捏滴死死的! 编程开发学习过程中遇到的瓶颈期,往往是由于看不 ...
- Typescript 学习笔记四:回忆ES5 中的类
中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...
- 面试官:你说你懂动态代理,那你知道为什么JDK中的代理类都要继承Proxy吗?
之前我已经写过了关于动态代理的两篇文章,本来以为这块应该没啥问题,没想到今天又被难住了- 太难了!!! 之前文章的链接: 动态代理学习(一)自己动手模拟JDK动态代理. 动态代理学习(二)JDK动态代 ...
随机推荐
- R: 用 R 查看、管理文件(夹)
文件管理主要函数: list.files( ): 查看当前目录下文件. file.show( ): 显示文件. file.access( ): 查看文件是否可读可写. file.create( ): ...
- c++ public protected private 继承
1:访问控制 1.1 public 这类型成员可以被类本身函数访问,也可以被外部创建的类对象调用.子类对象与子类内部可以访问 1.2 protected类型成员,只能被类本身函数访问.外部创建的类对象 ...
- ZROI2018提高day1t1
传送门 分析 在考场上我通过画图发现了对于n个点肯定用一个六边形围起来最优(假装四边形是特殊的六边形),我们发现可以将这个六边形分成两个梯形(梯形的高可以为0),然后我们便枚举两个梯形共同的底边和它们 ...
- Luogu 3267 [JLOI2016/SHOI2016]侦察守卫
以后要记得复习鸭 BZOJ 4557 大佬的博客 状态十分好想,设$f_{x, i}$表示以覆盖完$x$为根的子树后还能向上覆盖$i$层的最小代价,$g_{x, i}$表示以$x$为根的子树下深度为$ ...
- Dubbo项目入门
Dubbo是一款高性能.轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现. 它的特性如下 面向接口代理的高性能RPC调用 智能 ...
- Spring 特点
IoC:豆浆和打针的例子.一个是主动的,一个是被动的.比如在spring里面我们需要对象了,提出需求,spring容器会把对象给你.(这就是IoC) AOP:
- linux中的线程同步:生产者、消费者问题
#include <stdio.h> #include <semaphore.h> #include <unistd.h> #include <stdlib. ...
- Wet Shark and Bishops(思维)
Today, Wet Shark is given n bishops on a 1000 by 1000 grid. Both rows and columns of the grid are nu ...
- loj #6077. 「2017 山东一轮集训 Day7」逆序对
#6077. 「2017 山东一轮集训 Day7」逆序对 题目描述 给定 n,k n, kn,k,请求出长度为 n nn 的逆序对数恰好为 k kk 的排列的个数.答案对 109+7 10 ^ 9 ...
- Html再学
1. Html是网页的载体.内容就是网页制作者放在页面上想要用户浏览的信息,可以包括文字.图片.视频等. 2. CSS样式是展现.就像网页的外衣.比如,标题字体.颜色变化,或为标题加入背景图片.边 ...