对C++沉思录中代理类实现的质疑和修改

本文深入探讨并修正了经典C++参考书籍《C++沉思录》中关于代理类实现的问题,指出原始代码在对象赋值过程中导致了不必要的对象复制,并提出了解决方案以减少资源消耗和提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自http://blog.chinaunix.net/uid-25909722-id-2182249.html


《C++沉思录》是一本十分经典的C++参考书籍。虽然该书出版于1997年,但是,即使是现在读来,对学

习和掌握C++还是十分有价值的。一口气读到第五章《代理类》时,发现其中演示的例子似乎有不妥之处,
可以进行一定的改进。
第五章讲的是为属于同一个继承体系的对象设计一个容器。该容器要能容纳该继承体系中所有类的对象。
其中用到了代理类的方法来实现。实现的原理是:为每一个对象利用复制构造函数生产一个复制品,然后
以该复制品为参数构造一个他的代理对象,然后将代理对象保存在容器中,也就间接的将复制品对象保存
在了容器中。书中的代码如下(进行了很小的一些无关痛痒的改动):
  1. #include <iostream>
  2. using std::cout;
  3. using std::endl;

  4. class Vehicle {
  5. public:
  6.     virtual ~Vehicle(){}
  7.     virtual double weight() const = 0;
  8.     virtual void start() = 0;
  9.     virtual Vehicle* copy() const = 0;
  10.     // ...
  11. protected:
  12.     double weights;
  13. };

  14. class RoadVehicle: public Vehicle
  15. {
  16. public:
  17.     Vehicle* copy() const{
  18.         cout << "RoadVehicle.copy()" << endl;
  19.         return new RoadVehicle(*this);    //调用默认复制构造函数
  20.     }
  21.     virtual double weight() const { return weights;}
  22.     void start(){ cout << "RoadVehicle.start()" << endl; }
  23. };

  24. class AutoVehicle: public RoadVehicle
  25. {
  26. public:
  27.     Vehicle* copy()const{
  28.         cout << "AutoVehicle.copy()" << endl;
  29.         return new AutoVehicle(*this);    //调用默认复制构造函数
  30.     }
  31.     void start(){ cout << "AutoVehicle.start()" << endl; }
  32. };

  33. class Aircraft: public Vehicle
  34. {
  35. public:
  36.     Vehicle* copy()const{
  37.         cout << "Aircraft.copy()" << endl;
  38.         return new Aircraft(*this);    //调用默认复制构造函数
  39.     }
  40.     double weight() const { return weights;}
  41.     void start(){ cout << "Aircraft.start()" << endl; }
  42. };

  43. class Helicopter: public Aircraft
  44. {
  45. public:
  46.     Vehicle* copy()const{
  47.         cout << "Helicopter.copy()" << endl;
  48.         return new Helicopter(*this);    //调用默认复制构造函数
  49.     }
  50.     void start(){ cout << "Helicopter.start()" << endl; }
  51. };
  52. /*
  53.  * Vehicle类的代理类VehicleSurrogate:
  54.  */
  55. class VehicleSurrogate
  56. {
  57. public:
  58.     VehicleSurrogate(): vp(0) {}
  59.     VehicleSurrogate(const Vehicle& v): vp(v.copy()) { }
  60.     ~VehicleSurrogate(){ delete vp; }
  61.     VehicleSurrogate(const VehicleSurrogate& v): vp(v.vp ? v.vp->copy() : 0) {}
  62.     VehicleSurrogate& operator=(const VehicleSurrogate& v){
  63.         if(&!= this){
  64.             delete vp;
  65.             vp = (v.vp ? v.vp->copy() : 0);
  66.         }
  67.         return *this;
  68.     }
  69.     double weight() const{
  70.         if(vp == 0)
  71.             throw "empty VehicleSurrogate.weight()";
  72.         return vp->weight();
  73.     }
  74.     void start(){
  75.         if(vp == 0)
  76.             throw "empty VehicleSurrogate.start()";
  77.         vp->start();
  78.     }
  79.     // ...
  80. private:
  81.     Vehicle* vp;
  82. };

  83. int main()
  84. {
  85.     VehicleSurrogate parking_lot[10];
  86.     RoadVehicle rv;
  87.     AutoVehicle av;
  88.     Aircraft    airv;
  89.     Helicopter    heli;

  90.     parking_lot[0] = rv;
  91.     parking_lot[1] = av;
  92.     parking_lot[2] = airv;
  93.     parking_lot[3] = heli;

  94.     parking_lot[0].start();
  95.     parking_lot[1].start();
  96.     parking_lot[2].start();
  97.     parking_lot[3].start();

  98.     return 0;
  99. }
编译:g++ -Wall -o vehicle vehicle.cpp
运行:./vehicle
结果:
RoadVehicle.copy()
RoadVehicle.copy()
AutoVehicle.copy()
AutoVehicle.copy()
Aircraft.copy()
Aircraft.copy()
Helicopter.copy()
Helicopter.copy()
RoadVehicle.start()
AutoVehicle.start()
Aircraft.start()
Helicopter.start()
对于这个运行结果我们肯定会产生疑问:为什么对象的copy()函数会调用两次呢?
仔细分析代码不难看出:每一个这样的 parking_lot [ 0 ]   =  rv ;  赋值操作其中隐含会调用的函数为:
1.先调用VehicleSurrogate :: VehicleSurrogate ( const  Vehicle &  v ) :  vp ( v.copy() )   {   } 构造函数,
这里调用了一次copy()函数。通过该函数我们得到了一个类Vehicle 的代理类VehicleSurrogate 的对象。
2.对于等号=显然会调用VehicleSurrogate ::
VehicleSurrogate &  operator = ( const  VehicleSurrogate &  v ) 赋值操作符函数,这里又调用了一次
copy()函数。所以一次赋值操作:parking_lot [ 0 ]   =  rv 导致了两次copy()函数的调用。
导致的结果是:
在调用VehicleSurrogate :: VehicleSurrogate ( const  Vehicle &  v ) :  vp ( v.copy() )   {   } 构造函数时
v.copy()调用的是:
  1. Vehicle* RoadVehicle:: copy() const{
  2.       cout << "RoadVehicle.copy()" << endl;
  3.       return new RoadVehicle(*this); //调用默认复制构造函数
  4. }
这里调用了 RoadVehicle的默认复制构造函数,得到了一个类RoadVehicle的对象rv的一个复制体。
在调用VehicleSurrogate &  operator = ( const  VehicleSurrogate &  v ) 赋值操作符函数时,其代码
如下:
  1. VehicleSurrogate& operator=(const VehicleSurrogate& v){
  2.         if(&!= this){
  3.             delete vp;
  4.             vp = (v.vp ? v.vp->copy() : 0);
  5.         }
  6.         return *this;
  7. }
显然parking_lot [ 0 ]  和由VehicleSurrogate :: VehicleSurrogate ( const  Vehicle &  v ) 构造函数得到
的对象不是同一个对象,所以会执行: delete vp;将刚由类VehicleSurrogate的构造函数生产的对象
又释放掉,然后再一次执行:vp = (v.vp ? v.vp->copy() : 0); 实际上又调用了一次:
  1. Vehicle* RoadVehicle:: copy() const{
  2.       cout << "RoadVehicle.copy()" << endl;
  3.       return new RoadVehicle(*this); //调用默认复制构造函数
  4. }
函数。很显然这是十分的不合理的。因为这里是:调用了两次 RoadVehicle类的复制构造函数,
得到了 RoadVehicle类的两个对象,进而又释放掉其中一个对象。
显然合理的过程应该是:只调用一次RoadVehicle类的复制构造函数,得到一个存储在堆上的对象
然后将代理类VehicleSurrogate的对象的vp指针指向该对象即可。

既然找到了原因,那么如何修改代码呢?
很简单我们只要在代理类VehicleSurrogate中增加一个成员函数即可:
  1. void operator=(Vehicle& v){
  2.         vp = ((&!= 0)? v.copy() : 0);
  3. }
这样赋值操作的代码:parking_lot [ 0 ]   =  rv 就不会去调用VehicleSurrogate的构造函数和
赋值操作符函数了,而是直接调用我们定义的函数:
void VehicleSurrogate::operator=(Vehicle& v)。这样就避免了两次调用copy()函数。
最终修改的代码如下:
  1. #include <iostream>
  2. using std::cout;
  3. using std::endl;

  4. class Vehicle {
  5. public:
  6.     virtual ~Vehicle(){}
  7.     virtual double weight() const = 0;
  8.     virtual void start() = 0;
  9.     virtual Vehicle* copy() const = 0;
  10.     // ...
  11. protected:
  12.     double weights;
  13. };

  14. class RoadVehicle: public Vehicle
  15. {
  16. public:
  17.     Vehicle* copy() const{
  18.         cout << "RoadVehicle.copy()" << endl;
  19.         return new RoadVehicle(*this);    //调用默认复制构造函数
  20.     }
  21.     virtual double weight() const { return weights;}
  22.     void start(){ cout << "RoadVehicle.start()" << endl; }

  23. };

  24. class AutoVehicle: public RoadVehicle
  25. {
  26. public:
  27.     Vehicle* copy()const{
  28.         cout << "AutoVehicle.copy()" << endl;
  29.         return new AutoVehicle(*this);    //调用默认复制构造函数
  30.     }
  31.     void start(){ cout << "AutoVehicle.start()" << endl; }
  32. };

  33. class Aircraft: public Vehicle
  34. {
  35. public:
  36.     Vehicle* copy()const{
  37.         cout << "Aircraft.copy()" << endl;
  38.         return new Aircraft(*this);    //调用默认复制构造函数
  39.     }
  40.     double weight() const { return weights;}
  41.     void start(){ cout << "Aircraft.start()" << endl; }
  42. };

  43. class Helicopter: public Aircraft
  44. {
  45. public:
  46.     Vehicle* copy()const{
  47.         cout << "Helicopter.copy()" << endl;
  48.         return new Helicopter(*this);    //调用默认复制构造函数
  49.     }
  50.     void start(){ cout << "Helicopter.start()" << endl; }
  51. };

  52. /*
  53.  * Vehicle类的代理类VehicleSurrogate:
  54.  */
  55. class VehicleSurrogate
  56. {
  57. public:
  58.     VehicleSurrogate(): vp(0) {}
  59.     VehicleSurrogate(const Vehicle& v): vp(v.copy()) { cout << "VehicleSurrogate(const Vehicle& v)" << endl;}
  60.     ~VehicleSurrogate(){ delete vp; }
  61.     VehicleSurrogate(const VehicleSurrogate& v): vp(v.vp ? v.vp->copy() : 0) {}
  62.     VehicleSurrogate& operator=(const VehicleSurrogate& v){
  63.         if(&!= this){
  64.             cout << "VehicleSurrogate& operator=(const VehicleSurrogate& v)" << endl;
  65.             delete vp;
  66.             vp = (v.vp ? v.vp->copy() : 0);
  67.         }
  68.         return *this;
  69.     }
  70.     void operator=(Vehicle& v){
  71.         vp = ((&!= 0)? v.copy() : 0);
  72.     }
  73.     double weight() const{
  74.         if(vp == 0)
  75.             throw "empty VehicleSurrogate.weight()";
  76.         return vp->weight();
  77.     }
  78.     void start(){
  79.         if(vp == 0)
  80.             throw "empty VehicleSurrogate.start()";
  81.         vp->start();
  82.     }
  83.     // ...
  84. private:
  85.     Vehicle* vp;
  86. };

  87. int main()
  88. {
  89.     VehicleSurrogate parking_lot[10];
  90.     RoadVehicle rv;
  91.     AutoVehicle av;
  92.     Aircraft    airv;
  93.     Helicopter    heli;

  94.     parking_lot[0] = rv;
  95.     parking_lot[1] = av;
  96.     parking_lot[2] = airv;
  97.     parking_lot[3] = heli;

  98.     parking_lot[0].start();
  99.     parking_lot[1].start();
  100.     parking_lot[2].start();
  101.     parking_lot[3].start();

  102.     return 0;
  103. }
编译:g++ -Wall -o vehicle vehicle.cpp
运行:./vehicle
结果:
RoadVehicle.copy()
AutoVehicle.copy()
Aircraft.copy()
Helicopter.copy()
RoadVehicle.start()
AutoVehicle.start()
Aircraft.start()
Helicopter.start()
显然copy()函数只调用一次。
从中得到的一点理会:我们在阅读技术书籍的时候,一定要多问一个为什么,一定要深挖下去,要保持
怀疑的态度--尽信书不如无书
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值