类的实例在使用过程中,需要对它里面的值全清除后重赋值,
其中,字符串类型的成员用 CleanupInstance 可以全清空,
那其它类型如何处理?不会是一一赋空吧?呵呵

解决方案 »

  1.   

    不建议直接使用CleanupInstance,虽然这样子可以很简便,但是当存在接口类型(Interface)时就有可能出现非预期的结果.在文档当中有如下的提示:最好还是不要图简便,以防埋下安全隐患.
      

  2.   

    var
      Buffer: array[0..255] of Byte;
      Obj: TMyObject;
    begin
      //填充调试内容
      FillChar(Buffer, sizeof(buffer),$cc);  //清理内存
      FillChar(Buffer[0], TMyObject.InstanceSize,0);  //给对象指针指向有效的内存,相当于分配内存
      Obj := @Buffer[0];
      Obj := TMyObject(TMyObject.InitInstance(Obj));
      Obj.A := 65;
      Obj.B[0] := 'a';
      Obj.B[1] := '0';
      Obj.B[2] := '1';
      Obj.B[3] := 'c';
      Obj.B[4] := 'f';
      Obj.B[5] := '8';
      Obj.B[6] := '9';
      Obj.B[7] := #0;
      //这里请不要释放Obj
      ShowMessage(PAnsiChar(@Buffer[8]));  //清理内存
      FillChar(Buffer[0], TMyObject.InstanceSize,0);  //给对象指针指向有效的内存,相当于重新分配内存
      Obj := @Buffer[0];
      Obj := TMyObject(TMyObject.InitInstance(Obj));
      Obj.A := 65;
      Obj.B[0] := 'B';
      Obj.B[1] := '0';
      Obj.B[2] := '1';
      Obj.B[3] := 'c';
      Obj.B[4] := 'f';
      Obj.B[5] := '8';
      Obj.B[6] := '9';
      Obj.B[7] := #0;
      //这里请不要释放Obj
      ShowMessage(PAnsiChar(@Buffer[8]));
    end;
      

  3.   

    上面的代码只是演示一个最基本的实例进行内存管理和实始化的过程,但是不会调用类型(树)的构造函数.而所需要的结果是把内存进行重用,即在原有的内存基础上进行重新初始化.先给类型TMyObject增加一个构造函数:
    constructor TMyObject.Create;
    begin
      //这里只做简单演示,实际的代码当中,这里应当有一行"Inherited;"等类似的代码
      A := 100;
    end;然后把对成员A的赋值去掉
    var
      Buffer: array[0..255] of Byte;
      Obj: TMyObject;
    begin
      //填充调试内容
      FillChar(Buffer, sizeof(buffer),$cc);  //清理内存
      FillChar(Buffer[0], TMyObject.InstanceSize,0);  //给对象指针指向有效的内存,相当于分配内存
      Obj := @Buffer[0];
      Obj := TMyObject(TMyObject.InitInstance(Obj));
      //Obj.A := 65;//去掉给成员A的赋值
      Obj.B[0] := 'a';
      Obj.B[1] := '0';
      Obj.B[2] := '1';
      Obj.B[3] := 'c';
      Obj.B[4] := 'f';
      Obj.B[5] := '8';
      Obj.B[6] := '9';
      Obj.B[7] := #0;
      //这里请不要释放Obj
      ShowMessage(IntToStr(Obj.A));//查看成员A的值  //清理内存
      FillChar(Buffer[0], TMyObject.InstanceSize,0);  //给对象指针指向有效的内存,相当于重新分配内存
      Obj := @Buffer[0];
      //这里把代码改一下对TMyObject的构造函数进行调用
      //Obj := TMyObject(TMyObject.InitInstance(Obj));
      Obj.Create;//注意这一行代码  //Obj.A := 65;//去掉对成员A的赋值
      Obj.B[0] := 'B';
      Obj.B[1] := '0';
      Obj.B[2] := '1';
      Obj.B[3] := 'c';
      Obj.B[4] := 'f';
      Obj.B[5] := '8';
      Obj.B[6] := '9';
      Obj.B[7] := #0;
      //这里请不要释放Obj
      ShowMessage(IntToStr(Obj.A));//查看成员A的值
    end;然后再进行如下测试:
    var
      Obj: TMyObject;
    begin
      Obj := TMyObject.Create;//正常构造  ShowMessage(IntToStr(Obj.A));  //清理内存
      FillChar(Pointer(Obj)^,  TMyObject.InstanceSize,0);  //重新构造
      Obj.Create;
      ShowMessage(IntToStr(Obj.A));
    end;
      

  4.   

    需要注意的是最后通过调用TMyObject.Create分配出来的内存是需要释放的.而前面通过指向Buffer栈内存的是不需要释放的.
      

  5.   


    谢谢unsigned , 消化下先。呵呵。
      

  6.   

    我个人不建议使用unsigned所使用的方法清理内存,一个是因为Buffer在栈中,函数结束了,内存就释放了(当然可以提升Buffer为全局变量解决这个问题),再者,能通过这种方式初始化的对象,直接通过FillChar一样可以初始化。
    问题是,如果你的对象中包含带引用计数的成员(比如字符串)或者你的类中包含类,这么初始化会导致这些带引用计数的变量不能正确更新他们的引用计数以及类不能被正确释放而导致内存泄漏。另外,如果你的类实现了接口,还需要你按照正确的顺序在对象所指向的内存中初始化接口,这个顺序,Delphi并没有形成文档,也就是说,不同的Delphi版本可能代表这不同的顺序。
      

  7.   

    以下是在堆中实现 unsigned 所阐述的方法
    注意,使用这种方法的限制很多,在注释中给出的,仅仅是一部分限制。
    unit Unit1;interfaceuses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls;type
      TForm1 = class(TForm)
        Button1: TButton;
        Button2: TButton;
        procedure Button1Click(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;var
      Form1: TForm1;implementation{$R *.dfm}type
      TMyObject = class(TObject)
      private
        FA: Integer;
        FB: Char;
        FC: Byte;
        FD: Word;
        procedure SetA(const Value: Integer);
        procedure SetB(const Value: Char);
        procedure SetC(const Value: Byte);
        procedure SetD(const Value: Word);
      public
        property A: Integer read FA write SetA;
        property B: Char read FB write SetB;
        property C: Byte read FC write SetC;
        property D: Word read FD write SetD;
      end;{ TMyObject }procedure TMyObject.SetA(const Value: Integer);
    begin
      FA := Value;
    end;procedure TMyObject.SetB(const Value: Char);
    begin
      FB := Value;
    end;procedure TMyObject.SetC(const Value: Byte);
    begin
      FC := Value;
    end;procedure TMyObject.SetD(const Value: Word);
    begin
      FD := Value;
    end;{ TForm1 }procedure TForm1.Button1Click(Sender: TObject);
    var
      Obj: TMyObject;
    begin
      Obj := GetMemory(TMyObject.InstanceSize);//根据类的大小开辟一块内存
      try
        ZeroMemory(Obj, TMyObject.InstanceSize);//初始化这块内存    //这个过程可以完成对象的初始化,如果调用了这个,那么记得在最后需调用 CleanupInstance
        //他们是一对,这个调用不是必须的,视情况而定,
        //如果这里没有调用 InitInstance 则后门就不需要调用 CleanupInstance
        TMyObject.InitInstance(Obj);
        try
          //实际上,上面三个步骤(开辟内存,初始化内存,调用InitInstance完成的就是 NewInstance 所完成的工作)
          //这里调用Create仅仅是为了完成Create中对类中成员的初始化
          //但是 Delphi 却没有提供与之对应的可以释放类中成员占用的堆中的内存的调用
          //形式上 Destroy 似乎和 Create 对应,
          //但是 以对象的方式调用 Create 并不创建内存(TMyObject.Create 才创建内存),
          //仅仅是完成用户定义在 Create 实现过程中的初始化而已
          //而以对象的方式调用 Destroy 却真正释放了对象占用的内存
          Obj.Create;      Obj.A := 100;      ShowMessage(IntToStr(Obj.A));      //这里是清空刚刚的那个类的成员
          //仅仅是清空,
          //如果类中包含对象成员,则对象成员不会被释放
          //类中包含指针成员,指针指向的内存也不会被释放
          //类中包含字符串或者动态数组,则其占用的内存也不会被释放
          //类中包含接口,则接口的引用计数不会变化
          //总之,如果你这个类中的成员在堆中开辟了内存,那么这些内存都不会被释放
          //  并且引用计数类型的成员变量其引用计数也不会变化
          ZeroMemory(Obj, TMyObject.InstanceSize);
          TMyObject.InitInstance(Obj);
          Obj.Create;
          ShowMessage(IntToStr(Obj.A));
        finally
          Obj.CleanupInstance;
        end;
      finally
        FreeMemory(Obj); //记得用完了释放内存
      end;
    end;end.
    其实,你说的先释放再创建会很慢,可能是你的 Create 中做了很多工作导致创建变慢,如果你的应用频繁地需要创建、释放,那你可以优化你的Create,有些成员变量,完全可以用到了再初始化,没有必要非得在Create中一次都初始化了
      

  8.   

    只要Create(当然也可以是其它名称)是属于constructor,本身就会调用InitInstance,而不需要先手工调用一次InitInstance.
      

  9.   

    随便访问个虚方法或者 ClassName 之类的,程序就挂了……InitInstance 明明是在 NewInstance 里调用的,而且 InitInstance 还自带 FillChar(0)。要是既没虚方法,又没什么复杂的要包装的类型,直接用 record 就完了,还这么折腾干嘛CleanupInstance 这种方法对于由 rtl 自动管理生存期的成员类型都没问题,包括字符串、动态数组、接口等。没好办法自动处理的是其它对象,就算能判断出来是对象,也不知道在这里是否需要释放。
    一般来说,创建和释放对象的时候,初始化和清理工作消耗的时间远大于申请和释放内存的时间。如果只是为了省掉那么一小块时间,折腾太多工作得不偿失。你更应该做的是设计好你的类,而不是弄些出了问题都不好调的 trick。
      

  10.   

    谢谢17楼,我被我的调试器给欺骗...在Instance.Create前,需要InitInstance,这应该算是构建Class Frame.其实对于释放,确实没有什么好的办法,不管是CleanupInstance,或者Destroy都无法得到预期的效果.CleanupInstance,只能对已知的一些接口和结构体之类的进行释放,无从达到"全面"管理,而虚方法Destroy是一个Destructor,被内建了一个_FreeMem.如17楼所说,对于楼主类似的需求,如果是必要性的,建议使用结构体(record).或者自己内建一个Cleanup方法.如果觉得繁琐,在不影响实现的前提下,可以通过将相关的成员publish出来,然后通过RTTI处理.
      

  11.   

    还是把类成员publish出来,用RTTI做处理方便!
    我一直都是这么做:)