代理类

这里提到的代理类是指《C++ 沉思录》中提到的surrogate类,而不是设计模式proxy

代理类在书中是用来解决两个问题的:

  • 动态内存管理(double free)
  • 如何将多态类型塞进容器(new base-type(…) “slice down”)

而通过代理类保存指向基类的指针并由派生类实现虚拷贝函数可以完美解决上述两个问题

1
2
3
Vehicle* parking_lot[1000];		//由于Vehicle会出现slice现象,所以用指针
Automobile x=/*...*/
parking_lot[num_vehicles++]=new Automobile(x);

这个代码就有前面所说的两个问题:

如果要让parking_lot[p]指向一个新建的Vehicle,且这个Vehicle的类型和值与parking_lot[q]相同,会如何?

1
2
3
4
5
6
7
8
9
10
11
if(p != q)
{
delete parking_lot[p];
parking_lot[p]=parking_lot[q]; //会导致double free
}

if(p != q)
{
delete parking_lot[p];
parking_lot[p]=new Vehicle(parking_lot[q]); //Vehicle是基类类型,但我想知道派生类类型
}

虚拷贝函数解决多态问题

假设有一派生类为Truck,那么我们可以提供:

1
2
3
4
Vehicle* Truck::copy() const
{
return new Truck(*this);
}

然后应用于基类的copy-ctor和copy-operator=中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
VehicleSurrogate::VehicleSurrogate(nullptr){}
VehicleSurrogate::VehicleSurrogate(Vehicle const& v)
: vp(v.copy()){}

VehicleSurrogate::~VehicleSurrogate()
{
delete vp;
}

VehicleSurrogate::VehicleSurrogate(VehicleSurrogate const& rhs)
:vp(rhs.vp ? rhs.vp->copy() : nullptr){}

VehicleSurrogate& VehicleSurrogate::operator=(VehicleSurrogate const& rhs)
{
if(*this != rhs)
{
delete vp;
vp=(rhs.vp ? rhs.vp->copy() : nullptr);
}
return *this;
}

至于其他操作这里不是重点因此不赘述

1
2
3
VehicleSurrogate parking_lot[1000];		
Automobile x=/*...*/
parking_lot[num_vehicles++]=x;

这里最值得学习的就是给指针提供一个间接层放进容器中,这点十分优雅,可以让代码很直观

你可以试想在现代C++我塞shared_ptr(或unique/weak)到容器那画面不要太难看,而且提供间接层也意味着可以隐藏一些细节,毕竟客户不需要知道我是塞的多态类型,而只要知道他所需要的服务,这时我们将接口暴露出来也会显得更为自然

同时处理多态类型用到了虚拷贝构造函数,即所谓原型模式,可以解决以下问题:

1
2
3
4
5
6
7
8
9
class Base{/*...*/};
class D1 : public Base{/*...*/};
class D2 : public Base{/*...*/};

void f(Base* pd)
{
Base* pb=new D?(*pd);
//我要new谁?
}

这就是多态抽象带来的信息屏蔽,我们不知道传进来的是哪个派生类或者它就是基类,但是我们不能假设它为基类,因为这样会发生slice down,即派生类部分被切离
而通过基类派发clone(或copy)方法给派生类实现,如下:

1
2
3
4
5
6
7
8
9
10
//Base
virtual clone() const =0;

//D1
clone() const override
{ return new D1{*this}; }

//D2
clone() const override
{ return new D2{*this}; }

然后就可以开心的传基类指针(或引用)了:
1
2
3
4
void f(Base* pb)
{
auto pd=pb->clone();
}