首页 > 编程语言 >c++代理类

c++代理类

时间:2024-09-14 12:26:40浏览次数:9  
标签:const 对象 Vehicle RoadVehicle c++ 代理 lot parking

c++中代理类的学习

https://blog.csdn.net/lcg910978041/article/details/51468680

C++代理类是为了解决这样的问题: 容器通常只能包含一种类型的对象,所以很难在容器中存储对象本身。

怎样设计一个c++容器,使它有能力包含类型不同而彼此相关的对象? 

代理运行起来和他所代表的对象基本相同,但是允许将整个派生层次压缩在一个对象类型中。

代理类的每个对象都代表另一个对象,该对象可以是位于一个完整继承层次中的任何类的对象。通过在容器中使用代理对象而不是对象本身的方式,就是代理类的精髓思想所在。

1.为什么c++需要代理类

考虑如下的一个小实例:假设有一个类,命名为RoadVehicle,代表陆地上的车辆,简单的定义如下:

  1. //定义陆地上的车辆  
  2. class RoadVehicle  
  3. {  
  4. public:  
  5.     RoadVehicle(){} //默认构造函数,容许声明RoadVehicle的数组  
  6.     double get_weight()const  
  7.     {  
  8.         return 0.0;  
  9.     }  
  10. };  


现在我们需要用一个容器或者数组来保存这个类的一些列对象,那么我们可以如下声明数组来存放这些对象:

  1. RoadVehicle parking_lot[100];  


这样做会造成一个很明显的问题:当我们有另一类车辆    (比如飞机类AirCraft) 时,我们将RoadVehicle类和AirCraft类继承自同一个父类Vehicle,有如下的结构:

  1. class Vehicle  
  2. {  
  3. public:  
  4.     virtual double get_weight()const = 0;  
  5. };  
  6. //定义陆地上的车辆  
  7. class RoadVehicle:public Vehicle  
  8. {  
  9. public:  
  10.     RoadVehicle(){} //默认构造函数,容许声明RoadVehicle的数组  
  11.     double get_weight()const  
  12.     {  
  13.         return 0.0;  
  14.     }  
  15. };  
  16. //定义飞机  
  17. class AirCraft:public Vehicle  
  18. {  
  19. public:  
  20.     AirCraft(){} //默认构造函数,容许声明AirCraft的数组  
  21.     double get_weight()const  
  22.     {  
  23.         return 1.0;  
  24.     }  
  25. };  

有如上继承结构出现时,我们再来考虑如何用一个容器或者数组存放这些对象,尽管可以声明多个数组来存放,如下:

  1. RoadVehicle parking_lot1[100];  
  2. AirCraft parking_lot2[100];  

但是当我们的车辆的类型越来越多,比如还有AutoVehicle类,Helicopter类等等时,这种方式需要声明对应的数组来存放,很显然这不符合c++的精神。

既然RoadVehicle类和AirCraft类均继承自Vehicle类,那我们声明如下的数组来存放这些对象,又会发生什么事情呢?

  1. Vehicle parking_lot[100];  

但是这又会带来两个问题:

第一:Vehicle是一个抽象类,它拥有一个纯虚函数,它不能生成对象,因此他无法定义这样的数组;

第二:即使我们在vehicle中将纯虚函数修改为虚函数,使它可以生成对象,也会出现问题,比如有如下代码:

  1. RoadVehicle x;  
  2. parking_lot[num] = x;  

此时,会把x转换成一个Vehicle对象,同时会丢失所有在Vehicle类中没有的成员,然后将剪裁了的对象复制到parking_lot数组中去

解决上述两个问题的方法非常简单,即存储Vehicle对象的指针:

  1. Vehicle * parking_lot[100];  

此时,上述操作变为: 

  1. RoadVehicle x;  
  2. parking_lot[num] = &x;  

但是,这种方法又带来了新的问题:

由于x是局部变量,当x超出其作用域时,parking_lot数组中的指针变成了野指针这一问题可以如下解决:

  1. RoadVehicle x;  
  2. 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]呢?

显然:

  1. if(p != q)  
  2. {  
  3.     delete parking_lot[p];  
  4.     parking_lot[p] = parking_lot[q];  
  5. }  

这是不行的,这样会导致parking_lot[p]和parking_lot[q]指向同一个对象

  1. if(p != q)  
  2. {  
  3.     delete parking_lot[p];  
  4.     parking_lot[p] = new Vehicle(parking_lot[q]);  (这样的构造   是不对的   1 Vehicle无法产生对象     2 即使产生也不一定对   几乎可能会被裁剪掉  )
  5. }  

这也是不行的,因为Vehicle无法产生对象,即使能产生,也是被剪裁了的,是Vehicle对象, 而不是parking_lot[q]所指的对象的类型。  (因为具体指向的类型  我们不清楚)

事实上,这里的根本原因在于,我们无法知道parking_lot[q]所指对象的静态类型,c++中解决这一类问题是用多态。我们需要一个克隆函数当parking_lot[q]调用自己的克隆函数时,复制自己的一个副本,并返回一个该副本的指针。因此我们的代码变为如下:

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Vehicle  
  5. {  
  6. public:  
  7.     virtual double get_weight()const = 0;  
  8.     virtual Vehicle* copy() const = 0;  
  9. };  
  10. //定义陆地上的车辆  
  11. class RoadVehicle:public Vehicle  
  12. {  
  13. public:  
  14.     RoadVehicle(){} //默认构造函数,容许声明RoadVehicle的数组  
  15.     RoadVehicle(const RoadVehicle& RV){}  
  16.     double get_weight()const  
  17.     {  
  18.         return 0.0;  
  19.     }  
  20.     Vehicle* copy()const  
  21.     {  
  22.         return new RoadVehicle(*this);  
  23.     }  
  24. };  
  25. //定义飞机  
  26. class AirCraft:public Vehicle  
  27. {  
  28. public:  
  29.     AirCraft(){} //默认构造函数,容许声明AirCraft的数组  
  30.     AirCraft(const AirCraft& AC){}  
  31.     double get_weight()const  
  32.     {  
  33.         return 0.0;  
  34.     }  
  35.     Vehicle* copy()const  
  36.     {  
  37.         return new AirCraft(*this);  
  38.     }  
  39. };  

由于parking_lot数组中存放的都是Vehicle类型的指针,而其所实际指向的对象是Vehicle的任意子类因此,在释放parking_lot中指针所指向的内存时,我们需要虚析构函数,否则,会造成内存泄露,因此我们为每个类添加虚析构函数。代码如下:

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Vehicle  
  5. {  
  6. public:  
  7.     virtual double get_weight()const = 0;  
  8.     virtual Vehicle* copy() const = 0;  
  9.     virtual ~Vehicle(){}  
  10. };  
  11. //定义陆地上的车辆  
  12. class RoadVehicle:public Vehicle  
  13. {  
  14. public:  
  15.     RoadVehicle(){} //默认构造函数,容许声明RoadVehicle的数组  
  16.     RoadVehicle(const RoadVehicle& RV){}  
  17.     double get_weight()const  
  18.     {  
  19.         return 0.0;  
  20.     }  
  21.     Vehicle* copy()const  
  22.     {  
  23.         return new RoadVehicle(*this);  
  24.     }  
  25.     ~RoadVehicle(){}  
  26. };  
  27. //定义飞机  
  28. class AirCraft:public Vehicle  
  29. {  
  30. public:  
  31.     AirCraft(){} //默认构造函数,容许声明AirCraft的数组  
  32.     AirCraft(const AirCraft& AC){}  
  33.     double get_weight()const  
  34.     {  
  35.         return 0.0;  
  36.     }  
  37.     Vehicle* copy()const  
  38.     {  
  39.         return new AirCraft(*this);  
  40.     }  
  41.     ~AirCraft(){}  
  42. };  

如此一来,上面存在的问题,可以如下解决:

  1. if(p != q)  
  2. {  
  3.     delete parking_lot[p];  
  4.     parking_lot[p] = parking_lot[q]->copy();  //这一点 很重要!!!!!!!!!!!!   别忘记 这一步骤    delete parking_lot[p];  
  5. }  

上面的方法,在一定程度上解决了我们的问题,但是它在parking_lot数组中存储的是指针,这使得用户显示的处理内存分配,那么有没有一种方法既能够不显示的处理内存分配,又能够保持类Vehicle在运行时动态绑定呢?这就是我们的代理类了。

=======================================================================================================

我们为Vehicle类创建一个代理类,假设命名为VehicleSurrogate,每个VehicleSurrogate对象代表一个Vehicle对象只要VehicleSurrogate对象存在,那么它所关联的Vehicle对象就存在,复制VehicleSurrogate对象就会复制相对应的Vehicle对象,给代理赋新值也会先删除旧的关联的对象,再复制新的对象。

我们可以如下定义代理类:

  1. class VehicleSurrogate  
  2. {  
  3. public:  
  4.     VehicleSurrogate():vp(0){} //默认构造函数,使得可以声明VehicleSurrogate的数组  
  5.     
  6.    VehicleSurrogate(const VehicleSurrogate& VS)//以自身对象初始化   {  
  7.         vp  =   VS.vp   ?    (VS.vp->copy())  : (0);  
  8.     }  
  9.     VehicleSurrogate(const Vehicle& V):vp(V.copy()){}    //以它所代理的Vehicle对象初始化        这两个就是 定义了不同掉copy构造函数
  10.    
  11.  VehicleSurrogate& operator=(const VehicleSurrogate& VS)//重载赋值  {  
  12.         if(this != &VS)  {  //看看是不是自己    防止自我复制
  13.             delete this->vp;  
  14.             this->vp = VS.vp?VS.vp->copy():0;  
  15.         }  
  16.         return *this;  
  17.     }  
  18.     ~VehicleSurrogate()  {  
  19.         delete vp;  
  20.     }  
  21.   
  22.     //代理Vehicle类行为的函数,Vehicle有多少个行为函数,这里就需要重新定义多少个代理类函数  
  23.     double get_weight()const  {  
  24.         return vp->get_weight();  
  25.     }  
  26. private:  
  27.     Vehicle* vp;  
  28. };  

有了上述代理类之后,我们就可以如下操作: 

  1. VehicleSurrogate parking_lot[100];      !!!数组的定义会调用  num 次的默认构造函数 
  2.   
  3. RoadVehicle x;  
  4. parking_lot[0] = x;   注意:1先调用copy构造函数构造临时对象  上面两个copy选择其中一个                                                                                                                                               2再调用代理类的  operator =   函数(因为VehicleSurrogate 前面定义数组时候已经定义且默认初始化了  所以调用   = 函数 
  5. cout << parking_lot[0].get_weight();  

至此,我们利用代理类,就可以即不用显示进行内存分配管理,又可以使得Vehicle子类对象进行动态绑定。

========================================================================!!!!!!!!!!!!!!!!!

2. 智能指针

 “c++代理类(一)”中完成的简单代理类虽然解决了最急迫的问题,但效率上又存在了另外的问题,该简单类存在的问题主要是:

每个代理类对象都唯一关联一个实际对象,代理类对象存在则实际对象存在,代理类对象释放则实际类对象也要释放,且复制代理类对象就必须要复制实际类对象。

这在实际类很大的时候复制开销是非常大的。而且,代理类的复制会频繁的发生,比如:作为函数的参数进行值传递,或者作为函数的返回值等等。

我们将对该简单代理类进行改进,改进的思想主要是:在代理类中为其代理的实际对象添加一个标记,该标记指出有多少个代理类对象代理了这个实际对象,这样当我们复制代理类对象时,其实际所代理的对象就不需要复制了,只需要修改该标记即可。

其具体做法如下:

  1. class VehicleSurrogate  
  2. {  
  3. public:  
  4.         ......//跟简单类一样,唯一不同的是需要加入处理标记num的部分  
  5. private:  
  6.     Vehicle* vp;  
  7.     int * num; //添加的标记字段  
  8. };  

有了如上的结构,每次复制代理类时,只需要将(*num)++就可以了。其意义为:绑定到实际对象的代理类又多了一个

上述的策略在不需要修改实际对象时非常有用,即所有代理类对象只是读它所代理的实际对象时,但当某个代理类需要修改它所代理的实际对象时,问题就发生了,由于所有代理类对象实际所代理的对象在内存中是同一份,因此,一个代理类对象所做的修改将会影响其他代理类,因此,此时需要对所代理的实际对象进行复制,且该复制是无法避免的。我们称之为——写时复制。

实现指针的类代码如下: 

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Vehicle  
  5. {  
  6. public:  
  7.     Vehicle():weight(0.0){} //默认构造函数  
  8.     Vehicle(double w):weight(w){}   //含参构造函数
  9.   
  10.     virtual ~Vehicle(){}    //虚析构函数必须存在,所有子类对象在析构时都是以Vehicle*的方式调用析构函数的,以虚析构函数调用才能调用到正确的析构函数  才不会导致内存泄露  
  11.     virtual Vehicle* copy()const  //复制自己  {  
  12.         return new Vehicle(*this);  
  13.     }  
  14.     //读  
  15.     double get_weight()const   {  
  16.         return weight;  
  17.     }  
  18.     //写  
  19.     Vehicle* set_weight(double w)  {  
  20.         weight = w;  
  21.         return this;  
  22.     }  
  23. private:  
  24.     double weight;   
  25. };  
  26. //定义陆地上的车辆  
  27. class RoadVehicle:public Vehicle  
  28. {  
  29. public:  
  30.     RoadVehicle(){} //默认构造函数,容许声明RoadVehicle的数组  
  31.     RoadVehicle(double w):Vehicle(w){}  
  32.     RoadVehicle(const RoadVehicle& RV):Vehicle(RV.get_weight()){} //拷贝构造函数  
  33.   
  34.     Vehicle* copy()const   //复制自己  {  
  35.         return new RoadVehicle(*this);  
  36.     }  
  37.     ~RoadVehicle(){}  
  38. };  
  39. //定义飞机  
  40. class AirCraft:public Vehicle  
  41. {  
  42. public:  
  43.     AirCraft(){} //默认构造函数,容许声明AirCraft的数组  
  44.     AirCraft(double w):Vehicle(w){}  
  45.     AirCraft(const AirCraft& AC):Vehicle(AC.get_weight()){} //拷贝构造函数  
  46.   
  47.     Vehicle* copy()const  
  48.     {  
  49.         return new AirCraft(*this);  
  50.     }  
  51.     ~AirCraft(){}  
  52. };  
  53. //智能指针类定义  
  54. class VehicleSurrogate  
  55. {  
  56. public:  
  57.     VehicleSurrogate():vp(new Vehicle()),num(new int(1)){} //默认构造函数,使得可以声明VehicleSurrogate的数组  
  58.     VehicleSurrogate(const VehicleSurrogate& VS):vp(VS.vp),num(VS.num)//拷贝构造函数,可发现此时它所代理的实际对象并未复制  
  59.     {  
  60.         ++(*num);  
  61.     }  
  62.     VehicleSurrogate(const Vehicle& V):vp(V.copy()),num(new int(1)){}//以它所代理的Vehicle对象初始化  
  63.     VehicleSurrogate& operator=(const VehicleSurrogate& VS)//重载赋值,可发现此时它所代理的实际对象并未复制  
  64.     {  
  65.         if(this != &VS)  
  66.         {  
  67.             //删除原来的旧的关联对象  
  68.             if(--(*num) == 0 )  
  69.             {  
  70.                 delete vp;  
  71.                 delete num;  
  72.             }  
  73.             //赋值新的关联对象  
  74.             vp = VS.vp;  
  75.             num = VS.num;  
  76.   
  77.             ++(*num);  
  78.         }  
  79.         return *this;  
  80.     }  
  81.     ~VehicleSurrogate()  
  82.     {  
  83.         if(--(*num)== 0)  
  84.             {  
  85.                 delete vp;  
  86.                 delete num;  
  87.             }  
  88.     }  
  89.   
  90.     int get_num()const  
  91.     {  
  92.         return *num;  
  93.     }  
  94.     //代理Vehicle类行为的函数,读操作无需复制所代理的实际对象  
  95.     double get_weight()const  
  96.     {  
  97.         return vp->get_weight();  
  98.     }  
  99.     //写时复制策略,写时必须复制所代理的实际对象  
  100.     VehicleSurrogate& set_weight(double w)  
  101.     {  
  102.             if((*num) == 1)  
  103.             {  
  104.                 vp->set_weight(w);  
  105.             }  
  106.             else  
  107.             {  
  108.                 --(*num);  
  109.   
  110.                 vp = vp->copy();//真正的复制发生在这里  
  111.                 num = new int(1);  
  112.   
  113.                 vp->set_weight(w);  
  114.             }  
  115.         return *this;  
  116.     }  
  117.   
  118. private:  
  119.     Vehicle* vp;  
  120.     int * num;  
  121. };  
  122.   
  123. int main()  
  124. {  
  125.    //测试上述智能指针  
  126.     VehicleSurrogate parking_lot[100];  
  127.     RoadVehicle x(10);  
  128.     parking_lot[0] = RoadVehicle(x);  
  129.     parking_lot[1] = parking_lot[0];  
  130.     parking_lot[0].set_weight(5.0);  
  131.     cout << parking_lot[0].get_weight()<<endl<<parking_lot[0].get_num()<<endl;  
  132.     cout << parking_lot[1].get_weight()<<endl<<parking_lot[1].get_num()<<endl;  
  133. }  


使用智能指针,既保留了简单代理类的优点:无需显示管理内存分配,且能实现所代理的实际对象动态绑定,又省略了过多的复制开销。

转载:

c++ 代理类(一)_c++ 代理 和 多态-CSDN博客

c++代理类(二)_c++代理类太多的问题-CSDN博客

更多代理参考:

http://blog.csdn.net/wuzhekai1985/article/det

我的随笔 - 戒色 - 博客园

分类: c++

标签:const,对象,Vehicle,RoadVehicle,c++,代理,lot,parking
From: https://blog.csdn.net/quaer/article/details/142071747

相关文章

  • 代理工具
    nc简介Netcat是一个简单的Unix工具,用于在TCP或UDP协议上读取和写入数据。由于其简单、灵活的特性,它经常被用于网络调试或在各种网络脚本中。TCP协议概述TCP(传输控制协议)是一种面向连接的协议,用于在网络上可靠地传输数据。它提供了一种可靠的、有序的、基于字节流的数据传输服......
  • C++中定义一个空结构体的内存占用
    比如定义:structTest{};使用sizeof(Test)可以查看到内存占用并不是0,而是1字节。原因主要有两点:一是用于对象标识:在C++中,每个对象都需要有一个唯一的地址。如果空类或结构体的大小为0字节,那么创建两个这样的对象时,它们在内存中的地址将是相同的,这与C++的对象模型相冲突。为......
  • Nginx 4层代理获取客户端真实IP
    架构4层代理配置stream{upstreambackend{server10.4.7.30:80;}server{listen80;proxy_passbackend;proxy_connect_timeout1s;proxy_protocolon;#主要是把这个参数开上}}后端nginx配置注......
  • 内网穿透技术的思考--反向代理、TCP 隧道、 UDP 打洞--C++代码示例
    概述内网穿透是一种技术,用于在私有局域网(LAN)中的设备与外部网络(如互联网)之间建立通信通道,使得外部设备可以访问内网中的服务。由于内网设备通常位于防火墙或NAT(网络地址转换)设备之后,外部网络无法直接访问它们。因此,内网穿透技术旨在解决这一问题。本文将讨论如何使用C++实现......
  • vscode配置c/c++环境
    在VisualStudioCode(VSCode)中配置C/C++开发环境,可以帮助更加高效地编写和调试代码。以下是详细的步骤:1.安装VisualStudioCode确保已经安装了VSCode。如果还没有安装,可以从[VSCode官方网站](https://code.visualstudio.com/)下载安装程序并安装。2.安装C......
  • 反射&动态代理
    1.反射1.1反射的概述:**专业的解释(了解一下):**是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意属性和方法;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。**通俗的理解:(掌握)**......
  • C++-练习-39
    题目:编写一个程序,记录捐献给"维护合法权利团队"的资金。该程序要求用户输入捐献者数目,然后要求用户输入每一个捐献者的姓名和款项。这些信息被存在一个动态分配的结构数组中。每个结构有两个成员:用来存储姓名的字符数组和用力啊存储款项的double成员。读取所有的数据后,程序将......
  • 官网下载easyx压缩包,如何在devc++配置easyx
    视频教程官网下载easyx压缩包,如何在devc++配置easyxEasyXGraphicsLibraryforC++安装指南1.访问官网官网2.下载EasyX在官网上找到下载区域,点击下载按钮以获取EasyX安装包。3.访问更多下载选项点击页面上的“more”链接,以查看更多下载选项。4.下......
  • C++一元多项式解析、计算、输出(数据结构作业),可直接运行
    //Copyright(c)[email protected]#include<bits/stdc++.h>classPolynomial{private:std::unordered_map<int,int>data_;voidzero_value_optimization(){for(autoiter=data_.begin();iter!=data_.end();){......
  • 78_JAVA_new的使用在JAVA与C++的异同之处
    Java和C++都使用new关键字来创建对象和分配内存,但它们在实现和使用上有一些重要的异同之处。以下是这两种语言中new使用的主要异同点:1. 内存管理Java:自动内存管理:Java使用垃圾回收(GarbageCollection,GC)机制来自动管理内存。对象的生命周期由垃圾回收器自动管理,......