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

原创 2006年06月25日 11:49:00
一段旧代码,引起的关于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,他的作用就在于给了一个破坏堆栈的途径。
   
   

一段代码引起的思考(变量占用字节数及指针)

今天在学校bbs上看到一段代码 #include #include using namespace std; int main(){ int i,*pi = &i; char c...

一段简单的C代码引起的思考-局部字符串变量传参

#include #include const char * GetHello() { char *rtn = "hellodjfldsjfsadlfkja;sldjflaskdjfalksd...

一段代码引起的思考-------printf

看一小段代码: #include   int main() {        inta =5;        printf("%f\n" a);        return0; } ...

一段解决PHP BOM的问题的代码

  • 2012年02月25日 21:11
  • 1KB
  • 下载

C语言宏定义的学习------一个宏代表一段代码

开拓视野,以方便对代码的学习、研读和编写。 1\ #ifdef __MMI_BOOTUP_SCALE__ #define MMI_APP_INIT(func) {U32 start_time...

PHP 计算一段代码或一个页面的运行效率

在写一段PHP功能代码时,有时候很想知道自己写的代码到底效率怎么样。自己动手写了一个简单类,来计算任意一段代码或文件的运行效率(只包括运行时间、所占内存): /**  * 计算代码的运行效率 ...
  • koowave
  • koowave
  • 2012年11月06日 16:08
  • 143

用一段代码实现一个链表倒序(C++实现)

网上找的一个好的答案: #include "stdafx.h" #include using namespace std; struct Node { int value; Node...

将一段JS代码封装成一个方法

}).mouseout(function(){ $("#plate-3hover").removeClass("plate-3after") }); $("#plate-4").mousee...

结对编程就是两个人用同一个设计、同一个算法以及同一段代码,并且两人的角色可以随时互换;

结对编程就是两个人用同一个设计、同一个算法以及同一段代码,并且两人的角色可以随时互换;   结对编程的优点: 程序员互相帮助,互相教对方,可以得到能力上的互补。 ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:一段旧代码,引起的关于OO中一个问题的思考
举报原因:
原因补充:

(最多只允许输入30个字)