Recent

Author Topic: Raising an exception within a constructor after using self?  (Read 2762 times)

jamie

  • Hero Member
  • *****
  • Posts: 7421
Raising an exception within a constructor after using self?
« on: October 25, 2025, 08:08:59 pm »
If I have this ?

Code: Pascal  [Select][+][-]
  1. Constructor TTmpTextChild.Create(aOwner:TComponent);
  2. Begin
  3.   If (Not Assigned(aowner)) or (aOwner.ClassName=Self.ClassName) then
  4.     Raise Exception.Create('Owner can not be a text object');
  5.  Inherited Create(aOwner);
  6. end;                            
  7.  

Using the SELF to access the current class name and compare it to the owner coming in.

If an exception is raised here, does it not allocate an object and maybe return Nil?
The only true wisdom is knowing you know nothing

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11926
  • Debugger - SynEdit - and more
    • wiki
Re: Raising an exception within a constructor after using self?
« Reply #1 on: October 25, 2025, 08:12:48 pm »
IIRC, it will call the destructor. And that includes freeing the mem.

But it also means the destructor must be able to run.

At the point, when you raise the exception the instance mem has been zeroed. So the destructor can do stuff like
  if  {self.} SubObject <> nil then
      {self.} SubObject.Destroy;

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11926
  • Debugger - SynEdit - and more
    • wiki
Re: Raising an exception within a constructor after using self?
« Reply #2 on: October 25, 2025, 08:15:48 pm »
I don't know what it "returns"

I.e
MyFoo := TFoo.create; // exception in create

Not sure what MyTFoo will contain. But the instance should have been destroyed, and the mem freed.

Bart

  • Hero Member
  • *****
  • Posts: 5649
    • Bart en Mariska's Webstek
Re: Raising an exception within a constructor after using self?
« Reply #3 on: October 26, 2025, 05:34:34 pm »
You could use Fail in the constructor, but IIRC then the code creating it should check for the instance not being nil?

This:
Code: Pascal  [Select][+][-]
  1. type
  2.  
  3.   { TFoo }
  4.  
  5.   TFoo = class
  6.     constructor Create; overload;
  7.   end;
  8.  
  9. { TFoo }
  10.  
  11. constructor TFoo.Create;
  12. begin
  13.   raise exception.create('Foo');
  14. end;
  15.  
  16. var
  17.   Foo: TFoo;
  18.  
  19. begin
  20.    try
  21.    Foo := TFoo.Create;
  22.    finally
  23.      writeln('Assigned(Foo)=',Assigned(Foo));
  24.    end;
  25. end.
will say:
Code: [Select]
:\Users\Bart\LazarusProjecten\ConsoleProjecten>test
Assigned(Foo)=FALSE
An unhandled exception occurred at $0040175E:
Exception: Foo
  $0040175E  TFOO__CREATE,  line 17 of test.pas
  $0040184E  main,  line 27 of test.pas

Heap dump by heaptrc unit of C:\Users\Bart\LazarusProjecten\ConsoleProjecten\test.exe
127 memory blocks allocated : 3135/3456
125 memory blocks freed     : 3059/3376
2 unfreed memory blocks : 76
True heap size : 262144 (112 used in System startup)
True free heap : 261728
Should be : 261760
Call trace for block $01654238 size 64
  $0040177E  TFOO__CREATE,  line 18 of test.pas
  $0040184E  main,  line 27 of test.pas
Call trace for block $01625D88 size 12
  $00401772  TFOO__CREATE,  line 18 of test.pas
  $0040184E  main,  line 27 of test.pas

It looks like you get an unrecoverable memory loss here (you can call Free on the nil pointer, but it'll do just nothing).
It also looks like that if your constryctor can raise an exception, you have to test if the construction succeeded, just like you would have to do if you used Fail.

Bart

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11926
  • Debugger - SynEdit - and more
    • wiki
Re: Raising an exception within a constructor after using self?
« Reply #4 on: October 26, 2025, 06:08:44 pm »
It looks like you get an unrecoverable memory loss here (you can call Free on the nil pointer, but it'll do just nothing).
It also looks like that if your constryctor can raise an exception, you have to test if the construction succeeded, just like you would have to do if you used Fail.

The memory leak is the uncaught exception. put the whole thing into a "try except" with empty except handler, instead of the finally. And no leak will be there.

Code: Text  [Select][+][-]
  1. Heap dump by heaptrc unit of C:\Users\martin\AppData\Local\Temp\project1.exe
  2. 547 memory blocks allocated : 76214/76584
  3. 544 memory blocks freed     : 76022/76392
  4. 3 unfreed memory blocks : 192
  5. True heap size : 294912 (160 used in System startup)
  6. True free heap : 293888
  7. Should be : 293984
  8. Call trace for block $0000000001605A00 size 40
  9.   $000000010000A493  $fpc_getmem,  line 362 of ../inc/heap.inc
  10.   $0000000100010FBA  PushException,  line 361 of seh64.inc
  11.   $00000001000110DC  __FPC_default_handler,  line 395 of seh64.inc
  12.   $00007FFE3703296F
  13.   $00007FFE36FE2554
  14.   $00007FFE36FE22A7
  15.   $00007FFE347D5369
  16.   $0000000100010EC3  $fpc_reraise,  line 317 of seh64.inc
  17.   $0000000100001861  Create,  line 37 of project1.lpr
  18.   $0000000100001952  $main,  line 46 of project1.lpr
  19.   $0000000100001976  main_wrapper,  line 67 of system.pp
  20.   $0000000100010A40  Exe_entry,  line 181 of system.pp
  21.   $0000000100001734  _FPC_mainCRTStartup,  line 106 of sysinit.pp
  22.   $00007FFE35147374
  23.   $00007FFE36FDCC91
  24.   $0000000100001734  _FPC_mainCRTStartup,  line 106 of sysinit.pp
  25. Call trace for block $0000000001624060 size 128
  26.   $0000000100012FC3  TraceReAllocMem,  line 841 of ../inc/heaptrc.pp
  27.   $000000010000A46F  ReAllocMem,  line 350 of ../inc/heap.inc
  28.   $0000000100010D23  GetBacktrace,  line 264 of seh64.inc
  29.   $0000000100010E01  $fpc_raiseexception,  line 292 of seh64.inc
  30.   $0000000100001816  Create,  line 36 of project1.lpr
  31.   $0000000100001952  $main,  line 46 of project1.lpr
  32.   $0000000100001976  main_wrapper,  line 67 of system.pp
  33.   $0000000100010A40  Exe_entry,  line 181 of system.pp
  34.   $0000000100001734  _FPC_mainCRTStartup,  line 106 of sysinit.pp
  35.   $00007FFE35147374
  36.   $00007FFE36FDCC91
  37. Call trace for block $0000000001605900 size 24
  38.   $000000010000A3FA  Getmem,  line 284 of ../inc/heap.inc
  39.   $00000001000086B0  newinstance,  line 437 of ../inc/objpas.inc
  40.   $000000010001A2DA  Create,  line 178 of ../objpas/sysutils/sysutils.inc
  41.   $0000000100001804  Create,  line 36 of project1.lpr
  42.   $0000000100001952  $main,  line 46 of project1.lpr
  43.   $0000000100001976  main_wrapper,  line 67 of system.pp
  44.   $0000000100010A40  Exe_entry,  line 181 of system.pp
  45.   $0000000100001734  _FPC_mainCRTStartup,  line 106 of sysinit.pp
  46.   $00007FFE35147374
  47.   $00007FFE36FDCC91
  48.   $00000001000098F2  $fpc_initializeunits,  line 973 of ../inc/system.inc
  49.   $000000010000193E  $main,  line 45 of project1.lpr
  50.   $0000000100001976  main_wrapper,  line 67 of system.pp
  51.   $0000000100010A40  Exe_entry,  line 181 of system.pp
  52.   $0000000100001734  _FPC_mainCRTStartup,  line 106 of sysinit.pp
  53.   $00007FFE35147374

The instance itself is properly freed.

If you raise an exception in the constructor, then it does call "Destroy". So if there is a destructor (inherited) then that will run. Otherwise the default destructor runs. Either way the instance memory is freed.




You can also overwrite TObject.NewInstance
Then you can even prevent to allocate any memory in first.

However, in NewInstance you don't have the arguments that were passed to "Create", so you can't check what they are.

See below example.


Code: Pascal  [Select][+][-]
  1. program Project1;
  2. uses SysUtils;
  3.  
  4. type
  5.   TFoo = class
  6.   public
  7.     class function newinstance: tobject; override;
  8.     constructor Create;
  9.     destructor Destroy; override;
  10.   end;
  11.  
  12. class function TFoo.newinstance: tobject;
  13. begin
  14.   writeln('new');
  15. //  exit(nil);  // uncomment to test
  16.   Result := inherited newinstance;
  17. end;
  18.  
  19. constructor TFoo.Create;
  20. begin
  21.   inherited Create;
  22.   raise Exception.Create('');
  23. end;
  24.  
  25. destructor TFoo.Destroy;
  26. begin
  27.   writeln('destroy by exception');
  28.   inherited Destroy;
  29. end;
  30.  
  31. var
  32.   x: TFoo;
  33. begin
  34.   try
  35.     x := TFoo.Create;
  36.   except
  37.   end;
  38. end.
  39.  




you can also write a class function, instead of directly calling the constructor
Code: Pascal  [Select][+][-]
  1.   TFoo = class
  2.   public
  3.     class function Create: TFoo;
  4.     constructor DoCreate;
  5.  

And then in "Create" check your condition, and return nil if needed. Otherwise call the constructor.

« Last Edit: October 26, 2025, 06:12:23 pm by Martin_fr »

jamie

  • Hero Member
  • *****
  • Posts: 7421
Re: Raising an exception within a constructor after using self?
« Reply #5 on: October 26, 2025, 07:13:28 pm »
Ok, Ill look into this.
Thnaks.
The only true wisdom is knowing you know nothing

PascalDragon

  • Hero Member
  • *****
  • Posts: 6238
  • Compiler Developer
Re: Raising an exception within a constructor after using self?
« Reply #6 on: October 27, 2025, 10:12:20 pm »
I don't know what it "returns"

I.e
MyFoo := TFoo.create; // exception in create

Not sure what MyTFoo will contain. But the instance should have been destroyed, and the mem freed.

MyFoo will retain whatever value it contained before the assignment (be it a real value or some uninitialized garbage), because if an exception happens in the constructor then that assignment will never be executed.

n7800

  • Hero Member
  • *****
  • Posts: 587
  • Lazarus IDE contributor
    • GitLab profile
Re: Raising an exception within a constructor after using self?
« Reply #7 on: October 28, 2025, 02:15:33 am »
MyFoo will retain whatever value it contained before the assignment (be it a real value or some uninitialized garbage), because if an exception happens in the constructor then that assignment will never be executed.

So, do I need to explicitly use one of the following (example for a class field)?

Code: Pascal  [Select][+][-]
  1. try
  2.   FObj := TObject.Create;
  3. except
  4.   FObj := nil;
  5. end;

or

Code: Pascal  [Select][+][-]
  1. FObj := nil;
  2. FObj := TObject.Create;



Perhaps the compiler could be improved by assigning "nil" in this situation? I'm sure many users expect this.

dsiders

  • Hero Member
  • *****
  • Posts: 1497
Re: Raising an exception within a constructor after using self?
« Reply #8 on: October 28, 2025, 04:07:55 am »
MyFoo will retain whatever value it contained before the assignment (be it a real value or some uninitialized garbage), because if an exception happens in the constructor then that assignment will never be executed.

So, do I need to explicitly use one of the following (example for a class field)?

Code: Pascal  [Select][+][-]
  1. try
  2.   FObj := TObject.Create;
  3. except
  4.   FObj := nil;
  5. end;

or

Code: Pascal  [Select][+][-]
  1. FObj := nil;
  2. FObj := TObject.Create;



Perhaps the compiler could be improved by assigning "nil" in this situation? I'm sure many users expect this.

Don't know why I thought this, but... I always worked under the assumption that raising an Exception in a constructor was a bad idea. Maybe it's some cruft I brought along from my Delphi days (eons ago).
Preview the next Lazarus documentation release at: https://dsiders.gitlab.io/lazdocsnext

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1568
    • Lebeau Software
Re: Raising an exception within a constructor after using self?
« Reply #9 on: October 28, 2025, 04:57:12 pm »
So, do I need to explicitly use one of the following (example for a class field)?

I would say neither. If an object fails to construct, STOP. Something went wrong, don't ignore it. The object that contains the failed field is now incomplete.  Let the error propagate to whoever was trying to create that object.

Perhaps the compiler could be improved by assigning "nil" in this situation? I'm sure many users expect this.

I don't know anyone who expects a failed object construction to return a nil object pointer.  But if you really want that, move the construction into a factory function that returns nil on failure.

I always worked under the assumption that raising an Exception in a constructor was a bad idea. Maybe it's some cruft I brought along from my Delphi days (eons ago).

Raising an exception in a constructor is not a problem in Delphi.
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

n7800

  • Hero Member
  • *****
  • Posts: 587
  • Lazarus IDE contributor
    • GitLab profile
Re: Raising an exception within a constructor after using self?
« Reply #10 on: October 28, 2025, 08:23:56 pm »
I would say neither. If an object fails to construct, STOP. Something went wrong, don't ignore it. The object that contains the failed field is now incomplete.

Of course, that's why I want to explicitly return nil so I can check for it in dependent methods. Without it, I won't be able to distinguish garbage from an initialized class.

Let the error propagate to whoever was trying to create that object.

My code example above is just a demonstration, but here's a more practical example:

Code: Pascal  [Select][+][-]
  1. function ReadDefaultConfig: TStream;
  2. begin
  3.   //result := nil;
  4.   try
  5.     result := TFileStream.Create('this file not exist', fmOpenRead);
  6.   except
  7.   end;
  8. end;

I don't know anyone who expects a failed object construction to return a nil object pointer.

Now you know me (and the topic starter). When I read that the destructor is immediately called when an exception occurs, I assumed that nil was being assigned to the variable. I think it's logical to expect "nil" in a pointer for a class that wasn't created (was destroyed).

But if you really want that, move the construction into a factory function that returns nil on failure.

No, I don't want to create separate functions just for this task ))  BTW, what does "factory function" mean?
« Last Edit: October 28, 2025, 08:27:43 pm by n7800 »

n7800

  • Hero Member
  • *****
  • Posts: 587
  • Lazarus IDE contributor
    • GitLab profile
Re: Raising an exception within a constructor after using self?
« Reply #11 on: October 28, 2025, 08:25:23 pm »
I always worked under the assumption that raising an Exception in a constructor was a bad idea.

The problem is that methods can be called in a constructor, and those methods can call their own, and all of this can even call functions from other units. There's no guarantee that an exception (unhandled) won't occur anywhere, so it's potentially possible in any constructor (indirectly).

VisualLab

  • Hero Member
  • *****
  • Posts: 693
Re: Raising an exception within a constructor after using self?
« Reply #12 on: October 28, 2025, 11:32:25 pm »
  BTW, what does "factory function" mean?

A function implementing the pattern: factory.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11926
  • Debugger - SynEdit - and more
    • wiki
Re: Raising an exception within a constructor after using self?
« Reply #13 on: October 29, 2025, 12:44:25 am »
https://en.wikipedia.org/wiki/Factory_method_pattern

In your case you can simplify it to the "class function Create" example that I gave earlier.

n7800

  • Hero Member
  • *****
  • Posts: 587
  • Lazarus IDE contributor
    • GitLab profile
Re: Raising an exception within a constructor after using self?
« Reply #14 on: October 29, 2025, 02:00:51 am »
Thanks, I've been wanting to read about programming patterns for a while, but I never get around to it...

Meanwhile, I created a MR for the documentation, mentioning the immutability of the assigned variable and the likelihood of calling a destructor when initialization is incomplete.

 

TinyPortal © 2005-2018