一段旧代码,引起的关于OO中一个问题的思考

一段旧代码,引起的关于OO中一个问题的思考(1)
一段旧代码,引起的关于OO中一个问题的思考
       
    首先,我把本文的观点先提出来,作为下面论述的总纲:一个死掉的对象不能有任何行为。
    就是说,当一个对象已经被释放过以后,再通过他进行一些操作,我们认为是不合理的。尽管客观上讲,实际情况中这样做,程序是可以正常运行的。这个问题,目前只存在于类似c++,delphi这种不是生存期自管理的环境中,像java,c#之类的语法环境里,根本不会遇到这种事情。
    情况是这样的,比如有这样一段代码。从机器码角度上来说,他是能正常运行的。
/// c++
#include <iostream>
class Car {
public:
  void beep() {
    std::cout<<"Beep!!"<<std::endl;
  }
};
int main() {
  Car *car = new Car();
  car->beep();
  delete car;
  car->beep();
  return 0;
}

// delphi

program Demo;

{$APPTYPE CONSOLE}

type
  TCar = class(TObject)
  public
    procedure Beep();
  end;

procedure TCar.Beep;
begin
  Writeln('Beep!!');
end;

var
  ocar: TCar;
begin
  ocar := TCar.Create();
  ocar.Beep();
  ocar.Free;
  ocar.Beep();
end.


/
/// 运行结果,是意料中的
Beep!!
Beep!!

原因,就在于,car->beep();  实际上是被编译器解释成Car::beep(car);这种形式的。
也就是说,实际上,编译器不会去判断,car是否是一个正确的值。即使他已经被释放掉了,程序还是会正常的执行进去。
不过,当beep里面,用到了car里面的数据的时候,这个数据客观上已经不存在了,这样的话,就是一个内存访问错误。

举一个实际例子来说,一辆汽车已经被销毁了,我们不能指望汽车的灵魂作一些事情,这样是不太好的,虽然汽车灵魂还是能做一些事情的。

但是,这样的做法实际上是存在隐患的,如果有人一不小心,对灵魂调用了一些需要用到它的肉体(就是那些数据字段)的操作,发生内存访问错误。


//
//


一段旧代码,引起的关于OO中一个问题的思考(2)

    不过,实际的生活中,并不能总是这样完美,有的时候,人们还是会不得不遇到一些问题。一不小心,汽车就死了,人们继续对汽车的灵魂进行操作,隐患时时存在。

    于是,我们需要考虑一个新的问题,一个社会问题,自杀现象,在写程序的过程里,同样存在这样的问题。
一个例子就是,delphi中的Free函数,可以到system.pas单元里看看,他本来只是一个普通函数,他做到工作就是调用destroy把自己释放掉,就是结束自己。

    由于,free长久以来的存在,使得free这个名字已经成了一个特殊意义的函数,有经验的程序员们,总是能下意识的避免使用free之后的对象灵魂。

    同样,我们自己也可以作一个同样功能的函数,或许叫做die什么的,这时,我们还是会比较容易去犯这样的对对象灵魂进行操作的错误。这种情况我们是可以避免的。但是,下面这种情况,很难说了,在实际生活里面确实会遇到一些特殊些的情况。
   
    比如,A 调用 B, B调用C,C发现工作已经完成了,就把对象本身自己结束掉了,可以通过别的方法或者就本身做了这个操作。但是,比较不幸的是,A 和 B的调用堆栈,还是需要一一来结束的。就是说,若 A 和 B中,如同我们在(1)里面说的情况,没有对对象灵魂的数据进行操作,我们说,这个隐患被幸运的蒙混过去了。但是,通常情况下,A 和 B都会有一些操作的,难免需要访问一些数据,这样,程序就的出错了。
    有一个补救方式,也是,通常使用比较多的一种方式,用返回值来进行判断,使得下面的操作来区别对待。这个方式,我们说是起到了我们预期想要的作用的。
    但是,我要说的是,这样不好,我们的代码会不得不变得比较丑陋和混乱,大量的条件语句,不停的去判断是否需要继续处理,多个连续判断之后,我们的代码的执行路径的分支很快以级数上升。这个是我们很不想看到的一个结果,他的副产品是在有些让人不爽。
   
   
   
   
   
    而且,我们不得不提一个额外的问题,我们的程序不仅仅是一条路线,还有windows的消息机制,对我们带来的一些影响,使得复杂度更加上升,问题也更加隐蔽了。我们知道,windows的消息机制, sendmessage, postmessage两种,他们的实际的本质就是,调用注册的处理函数,不管VCL怎么封装,最后,还是会调用我们自己写的函数里。
   
    这时,我们可以看到,我们的系统栈受到了两种因素的影响,第一,程序里面的调用,是一种顺序的调用,从头至尾;还有一种是事件式的。这两种机制,组成了我们的windows编程,系统栈也在这两种调用方式里面,压栈,退栈。很完美,也很顺畅,对前辈们的想象力表示一下钦佩。
   
    但是,这种简单而清晰的机制毕竟不是全部。我们说,规则是用来破坏的,总会有这样那样的理由,在一些机制下是涵盖不了的。
    这就是另一个问题,比如,计算机从汇编,发展到c。从机器码,过渡到面向过程。不知道哪位老大,提出的结构化的编程。以一种革命性的方式,使得程序的生产力,上升了一大截,不过,代价就是,必须放弃一些汇编语言里面,有些几乎可以叫做神来之笔,但在结构化编程里面肯定是异端,是破坏者的方式,举一些例子,jmp 指令。要知道,我们的图灵机里要求的是,一条很长的磁带,一个指针,一个状态表,这就是一台全部的计算器了。而jmp指令,就是控制这个指针的。于是,很多变态的方式出现了,那是一个漫长的故事。有兴趣的,我们可以另外再讨论。
   
    熟悉c语言的人,可能会记得,有一个离奇的函数,setjmp,或者是 setlongjmp,他的作用就在于给了一个破坏堆栈的途径。
   
   
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值