Recent

Author Topic: Building a Class hierarchy  (Read 4757 times)

Nitorami

  • Sr. Member
  • ****
  • Posts: 497
Building a Class hierarchy
« on: February 21, 2015, 05:24:05 pm »
I am looking for a bit of advice on good practice with Class hierarchies. My small console program is working fine so far, but it left me with a few general questions.

My Class hierarchy is based on my own Class TNode, which itself may not be instantiated.

1. Should TNode inherit from something such as TObject ? Why ? If yes, should it then call Inherited create / destroy ? It seems to work either way and I can't see a benefit in inheriting from TObject so I threw it out. Or does any Class inherit from TObject implicitly ?

2. Error handling: I understand that error handling is required for anything that may be expected to fail such as interaction with the user, disc or network I/O, or allocating a lot of memory. However I have seen code where the success of each inherited create in the hierarchy is checked, leading to awkward queues of try try except finally etc.
My own Classes only allocate a few bytes of memory, do not access disc nor network, and the number of instances is limited, so I cannot see a reason why create would fail. Except if there was only a few bytes of heap left, but then there would hardly be enough memory for error handling / continuing the program in the first place, so it can as well crash. Can anyone point me to some advice on good practice in error handling ?

Regards
Martin




Bart

  • Hero Member
  • *****
  • Posts: 5290
    • Bart en Mariska's Webstek
Re: Building a Class hierarchy
« Reply #1 on: February 21, 2015, 06:37:06 pm »
1. Should TNode inherit from something such as TObject ? Why ? If yes, should it then call Inherited create / destroy ? It seems to work either way and I can't see a benefit in inheriting from TObject so I threw it out. Or does any Class inherit from TObject implicitly ?

These two definitions are equal to one another:
Code: [Select]
type
  TClass1 = Class
  end;
//and
  TClass2 = Class(TObject)
  end;

So, you TNode inherits from TObject.

TObject.Create is not virtual, so it cannot be overriden.
You should call inherited destroy in the destructor.
(You only need an overridden destructor if you need e.g. cleaning up/freeing things.)

Code: [Select]
destructor TMyClass.Destroy;
begin
  //Clean up and free all memory we allocated
  inherited Destroy;
end;

2. [snip] My own Classes only allocate a few bytes of memory, do not access disc nor network, and the number of instances is limited, so I cannot see a reason why create would fail. Except if there was only a few bytes of heap left, but then there would hardly be enough memory for error handling / continuing the program in the first place, so it can as well crash. Can anyone point me to some advice on good practice in error handling ?

If creating the object fails dus to lack of memory an EOutOfMemory exception will be raised (the exception object of this is created when your program starts, otherwise creating it may fail due to the lack of memory later on).

Best practice is use like this:

Code: [Select]
...
  MyVar := TMyClass.Create;
  try
    //do stuff with MyVar
  finally
    //this will always be executed, even if doing stuff with MyVar raises an exception
    MyVar.Free;
  end;
...

Bart

Nitorami

  • Sr. Member
  • ****
  • Posts: 497
Re: Building a Class hierarchy
« Reply #2 on: February 21, 2015, 06:54:49 pm »
Alright. But, in your example

Code: [Select]
MyVar := TMyClass.Create;
  try
    //do stuff with MyVar

at the time I try to do stuff with MyVar, the compiler must already have created the object in memory, isn't it ? So if this has failed where can I intercept it ?

Bart

  • Hero Member
  • *****
  • Posts: 5290
    • Bart en Mariska's Webstek
Re: Building a Class hierarchy
« Reply #3 on: February 21, 2015, 07:41:33 pm »
Assuming your class does nothing in it's constructor that can raise exceptions, there should be no need for this.
At this point if it fails, this will be due to some error elsewhere which wasn't caught, and you program will (and probably should) crash.

If your constructor can raise exceptions, and the constructor does not handle them, then MyVar will not be instantiated (will remain nil), and you will see the exception.
Mostly unhandled exceptions in a constructor will mean that something is catastrophically wrong, en even if you catch the exception, the program probably will not run properly.

See what happens when the constructor fails in the next program:

Code: [Select]
program test;
//{$mode objfpc}
//{$h+}
uses SysUtils;

type

  { TMyClass }

  TMyClass = Class
    constructor Create;
  end;
var
  MyVar: TMyClass;

{ TMyClass }

constructor TMyClass.Create;
begin
  if (paramcount > 0) then raise exception.create('TMyVar construction exception');
end;

begin
  Randomize;
  try
    try
      MyVar := TMyClass.Create;
      writeln('Addr(MyVar) = ',PtrInt(MyVar));
    except
      on E: Exception do writeln('Exception: ',E.Message,LineEnding,'Addr(MyVar) = ',PtrInt(MyVar));
    end;
  finally
    if Assigned(MyVar) then
    begin
      write('Free MyVar ... ');
      MyVar.Free;
      writeln('Done.');
    end
    else
    begin
      writeln('MyVar = nil, no need to free it.');
    end;
  end;
end.

Code: [Select]
C:\Users\Bart\LazarusProjecten\ConsoleProjecten>test
Addr(MyVar) = 23023104
Free MyVar ... Done.
Heap dump by heaptrc unit
53 memory blocks allocated : 986/1208
53 memory blocks freed     : 986/1208
0 unfreed memory blocks : 0
True heap size : 196608 (112 used in System startup)
True free heap : 196496

C:\Users\Bart\LazarusProjecten\ConsoleProjecten>test  1
Exception: Random exception
Addr(MyVar) = 0
MyVar = nil, no need to free it.
C:\Users\Bart\LazarusProjecten\ConsoleProjecten>test
Addr(MyVar) = 23023104
Free MyVar ... Done.
Heap dump by heaptrc unit
53 memory blocks allocated : 986/1208
53 memory blocks freed     : 986/1208
0 unfreed memory blocks : 0
True heap size : 196608 (112 used in System startup)
True free heap : 196496

C:\Users\Bart\LazarusProjecten\ConsoleProjecten>test  1
Exception:TMyVar construction exception
Addr(MyVar) = 0
MyVar = nil, no need to free it.
Heap dump by heaptrc unit
56 memory blocks allocated : 1086/1312
56 memory blocks freed     : 1086/1312
0 unfreed memory blocks : 0
True heap size : 229376 (128 used in System startup)
True free heap : 229248

As you see fpc cleans up the mess, there is no unfreed memory when the constructor fails.

Bart

Nitorami

  • Sr. Member
  • ****
  • Posts: 497
Re: Building a Class hierarchy
« Reply #4 on: February 21, 2015, 08:08:38 pm »
Ah ! I see... thank you!

But this brings me to another issue I never understood: You are using MyVar.Free to remove MyVar from memory. But usually TMyClass would have a destructor which should be called first, or not ?

I'm asking this because:

I have objects organised in a TFPHashObjectList.

The documentation says that if the list is created with parameter TRUE (the default), then the list owns the objects: when an object is removed from the list, it is destroyed (freed from memory). Clearing the list will free all objects in the list.

However on list destroy, the list indeed seems to FREE the objects, but never calls their destructor. Shouldn't FREE implicitly call the destructor of the object ?

I'm rather confused about the dependancies of Free, destroy, FreeAndNil, Done etc..

Oh, I'm sorry. It does work if I declare
TNode.Destroy; OVERRIDE;

SO: FREE calls destroy first, but I need to override destroy if I want my code to be executed.
FREE does however not set the object to NIL.
FreeAndNIL (MyVar) does all of it.

« Last Edit: February 21, 2015, 08:45:21 pm by Nitorami »

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11455
  • FPC developer.
Re: Building a Class hierarchy
« Reply #5 on: February 21, 2015, 08:42:24 pm »

But this brings me to another issue I never understood: You are using MyVar.Free to remove MyVar from memory. But usually TMyClass would have a destructor which should be called first, or not ?

Free is basically a helper method that does

Code: [Select]
if self<>nil then self.destroy;
IOW it calls the destructor with an extra NIL check.
 

 

TinyPortal © 2005-2018