您的位置:首页 > 编程语言 > C语言/C++

《C++ 沉思录》阅读笔记——代理类

2015-07-02 20:51 363 查看
《C++ 沉思录》阅读笔记——代理类

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。/article/4572123.html

Andrew Koenig 和 Barbara Moo 堪称C++研究领域的”第一神仙眷侣”,看他们的书非常有条理性。这次要解释的是C++中的另一个常见问题。

找出一种优美的控制内存分配的方法来绑定不同子类对象到容器中。多么复杂的一句话,莫慌,其实很简单,跟着步伐来看。

首先假设我们要设计一系列交通工具的类,一般来说我们会定义一个交通工具的基类,里面存放所有交通工具都有的成员和属性,比如这样:

然后会有一些交通工具继承关系,比如这样:

现在我们要定义一个容器,来保存不同类型的交通工具。

这个要求看起来简单,但没有想象中那么容易。化繁为简,比如我们用一个数组来保存不同的交通工具,首先我可能会这么写:

仔细一想,这么写好像不对,为什么呢?因为 Vehicle 里面有纯虚函数,所以 Vehicle 是个抽象类,抽象类是不会有对象的,所以这么定义是肯定不行的。一般分析也就到这里为止了,但继续想一下,如果我把
Vehicle 中的所有纯虚函数去掉,那么这种定义好像就是OK的,语法上不会有问题,但是有另一个问题,比如下面这样的赋值:

这样的赋值会导致 Helicopter
对象被转换成一个Vehicle对象,它将丢失自己的 Helicopter 属性,这可不是我们想要的,这就好像把一个 double 数转换成整型放进整形数组里,丢失了自己的小数部分。

看到这里,马上有人会提出,那么在 parking_lot
中存储 Vehicle 的指针不就可以了吗?我们一起来看看:

然后我们重复上面的赋值操作:

看起来一切OK,但是有经验的程序员(比如说我,:))一眼就看出这里很危险,为什么危险呢?因为存储指针本身就是一件危险的事情,具体说来,这里的 x 看起来是一个局部变量,如果
x 被释放掉了,那么 parking_lot 数组里的指针立马成了悬垂指针,指向什么内容就不知道了。一个富有责任心的程序员是铁定不会这么干的。

那我们是不是就没折了呢?也不是,既然放指针不行,那么我复制一下这个对象算了,如下:

虽然浪费了些时间和内存,但是这么做看起来确实可以,自己分配了内存当然要由自己来释放,所以我们继续规定在 delete 这个 parking_lot
的时候,我们也释放其中所指向的对象。如果这么干只有自己管理内存这么一个负担的话,我想我还能接受,但是这里有一个不那么明显的问题。就是我们放入parking_lot 中的对象,必须要是已知类型的对象,一说到这里有的看官就立马明白了我的意思了,也就是说对于那些编译时类型未知的对象,这里就没办法保存了,举个例子,比如我需要在parking_lot[p]
中放 parking_lot[q] 的对象,该怎么办呢?我们并不知道 parking_lot[q]
的对象类型,所以我们没办法复制这个对象,同时,我们不能让 parking_lot 中有两个指针指向同一个对象,因为我们在删除这个容器时会把里面的对象也删掉,如果有两个指针指向同一个对象那么就会删除两次。当然,你可以用别的方法来避免,但这还是让我无法忍受了。

对于编译时的未知对象,聪明的程序员已经想到办法解决了。为什么我们要知道它们是什么?只要它们自己知道自己是什么,然后告诉我们就OK了呗!good boy!说明白些,就是我们可以让继承自
Vehicle 的类来告诉别人他们到底是什么,一个简单的办法就是在 Vehicle 中定义的 copy 的纯虚函数,然后继承自 Vehicle 的类都设计自己的 copy 函数,用来把自己复制一份返回给调用者,这样调用者就不用知道这些乱七八糟的交通工具是什么了。我们来继续修改代码:

然后我们修改 Helicopter 类,增加一个 copy 函数:

这样我们就再也不需要知道x的类型或者是 parking_lot[q] 的类型了,直接调用 x.copy() 函数或者parking_lot[q]->copy()
函数就OK了。

我们完美的解决了上面提到的第二个问题,但程序员从来都是追求完美的,那么我们有办法解决这个显示处理内存分配的问题吗?这也是程序员幸福的地方,别的领域追求完美是极其困难的,但代码总能让我们欣喜。《C++ 沉思录》里提到了一个非常深刻的概念——“用类来表示概念”,到底是个什么意思呢?就是说我们设计类,不光可以是一个具体的事物,同样,也可以是一个概念,比如,你可以用类来表示人,男人,女人等等,同样你可以用类来表示家庭,人是具体的,而家庭只是一个概念,家庭里肯定有有人,所以把控了家庭这个概念,也就把控了人(不要跟我抬杠说有些人没有家庭,举个例子而已,亲!)。

具体表现在代码上就是我们通过定义一个代理类,来表达这些不同的交通工具,这个代理类应该可以代表不同的交通工具,同时它需要帮助我管理内存,而且需要能够实例化,因为这样我就不用再纠结上面那个
Vehicle 是抽象类没办法定义容器的问题,所以,这个代理类的作用是让我能够定义代理类的容器,同时不需要我来考虑内存的管理问题,而且要支持编译时类型未知的情况。

代理类只是一个管理交通工具的管理者,它不是一个具体的东西,就跟大明星的经纪人一样。那看来它必须保存一个明星,也就是得有一个指向交通工具的指针,同时它需要上台面,那么它需要真实的构造函数,同时它需要能够放进容器,所以它需要一个默认构造函数:

上面多加了几个构造函数和赋值操作符,也不难理解,毕竟是一个真实的类嘛。其中以 const Vehicle& 为参数的复制构造函数就提供了为任意交通工具做代理的能力。一切看起来OK,但是在默认构造函数里我们能够为 p
指针赋值什么呢?好像只能赋为0了。这个零指针也就是说通常说的空代理。那么让我们来完成这个代理类的成员函数吧:

这里没有什么多余的秘密了,仔细点都OK。写到这里我们终于可以定义一个完美的 parking_lot 了。

总结一下:

当我们使用继承和容器的时候,通常需要处理两个问题:内存的分配和编译时类型未知对象的绑定。使用一个被成为代理类的东西,我们把复杂的继承层次压缩到了一起,让这个类能够代表所有的子类型,用类来表示概念的武器果然犀利。

本文出自 “菜鸟浮出水” 博客,请务必保留此出处/article/4572123.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: