资源需要分配,同样也是需要销毁的.<程序员修炼之道>中原则35,"finish what you start"说的便是这个道理..
下面是一个很糟糕的程序,它打开文件,读取信息,更新字段,然后写回结果.'
FILE *fd; void readObject(const char *filePath, Object *obj) { fd = fopen(filePath, "r+"); fread(obj, sizeof(*obj), 1, fd); } void WriteObject(Object *obj) { rewind(fd); fwrite(obj, sizeof(*obj), 1, fd); fclose(fd); } void updateObject(const char *filePath, int weight) { Object obj; readObject(filePath, &obj); obj.weight = weight; writeObject(&obj); }
乍看起来,程序可以工作得很好.但是,因为共享fd,readObject和writeObject却紧紧的耦合在一起了.可以很轻松的举一个例子,证明两个函数耦合带来的问题.比如说,要求仅在weight大于100时才做更新,那么updateObject函数将被修改为,
void updateObject(const char *filePath, int weight) { Object obj; readObject(filePath, &obj); if(weight > 100) { obj.weight = weight; writeObject(&obj); } }
这就引入了一个问题,WriteObject在某些情况下并不会被调用,以致于文件未被关闭,这将浪费系统的资源.
一种解决方案可以是
void updateObject(const char *filePath, int weight) { Object obj; readObject(filePath, &obj); if(weight > 100) { obj.weight = weight; writeObject(&obj); } else fclose(fd); }
这看起来貌似不错,不管新的weight是多少,文件总是会被关闭..但其实,这是一个非常糟糕的修改,readObject,writeObject和updateObject三个函数由于共享fd耦合在一起了.简单的web服务器中的代码就是如此,爬出一个陷阱,却掉进了一个更大的坑,这不得不说是种悲剧.
要解决这个问题,可以遵守一个原则,资源的分配和销毁这一责任由同一个函数承担,打开和关闭在同一地方,而且配对出现.重构如下:
void readObject(FILE *fd, Object *obj) { fread(obj, sizeof(*obj), 1, fd); } void WriteObject(FILE *fd, Object *obj) { rewind(fd); fwrite(obj, sizeof(*obj), 1, fd); } void updateObject(const char *filePath, int weight) { FILE *fd; Object obj; fd = fopen(filePath, "r+"); readObject(fd, &obj); if(weight > 100) { obj.weight = weight; writeObject(fd, &obj); } fclose(fd); }
这样做还有一个附加的好处--去掉丑陋的全局变量FILE *fd.
异常下的资源分配和释放
在使用异常时,很容易写出这样的代码,
void doit() { Node *n = new Node; try { // do something } catch(...) { delete n; throw; } delete n; }
注意到,n是在两个地方被释放的,一次是在程序正常路径上,一次是在异常处理中.这显然是违反DRY(Dont repeat yourself)原则的,也许会像前面一样导致维护问题.
解决上面的问题,其实可以用到3种小技巧.一是,利用C++的性质--局部变量在作用域外会被自动销毁.如果允许的话,我们可以将'n'从指针变为栈上的实际的对象.
void doit1() { Node n; try { // do something } catch(...) { throw; } }
这样,不管是否抛出异常,C++都将自动析构Node对象.
当然,由于各种原因,用对象替代指针的做法可能是不被允许的.变通方法很简单,我们可以将指针包装在另外一个类中,
class NodePointer { Node *n; public: NodePointer() { n = new Node; } ~NodePointer() {delete n; } }; void doit2() { NodePointer n; try { // do something } catch(...) { throw; } }
最后一种方法是直接利用C++库中的模板类auto_ptr,它将自动包装动态分配的对象.
void doit3() { auto_ptr<Node> p (new Node); // 用p->访问. try { // do something } catch(...) { throw; } }