Recent

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

creaothceann

  • Full Member
  • ***
  • Posts: 196
Re: Raising an exception within a constructor after using self?
« Reply #15 on: October 29, 2025, 02:56:01 am »
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 [sic]', fmOpenRead);
  6.   except
  7.   end;
  8. end;

Perhaps offtopic, but imo coding like this is not advisable, because it accepts the use of exceptions for something that is not a bug in the program (files are global resources and can disappear for valid reasons between FindFirst/FindNext and TFileStream.Create). I'd rather use something like SysUtils.FileOpen. (In the error case it also doesn't stop the program when it's run from the IDE.)


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).

x := SomeClass.Create is two separate things: object creation, and an assignment. The latter is skipped when the exception occurs. x has to be either initialized beforehand, or cleared in the exception handler.
« Last Edit: October 29, 2025, 03:07:11 am by creaothceann »
And don't start an argument, I am right.

n7800

  • Hero Member
  • *****
  • Posts: 542
Re: Raising an exception within a constructor after using self?
« Reply #16 on: October 30, 2025, 06:44:50 am »
Perhaps offtopic, but imo coding like this is not advisable, because it accepts the use of exceptions for something that is not a bug in the program (files are global resources and can disappear for valid reasons between FindFirst/FindNext and TFileStream.Create). I'd rather use something like SysUtils.FileOpen. (In the error case it also doesn't stop the program when it's run from the IDE.)

1. I don't throw an exception - a standard function does that. I'm actually handling it.
2. The hypothetical "ReadDefaultConfig" function should return a TStream, while SysUtils.FileOpen returns a file handle.

x := SomeClass.Create is two separate things: object creation, and an assignment. The latter is skipped when the exception occurs.

Well, as far as I know, calling a function creates a stack frame, and the function's result is passed through it. So, I assumed that when the function exits, it should still return something, which will be assigned to a variable. It seems there are still more implementation details...

The user isn't required to know this anyway, so the documentation should explain it.

n7800

  • Hero Member
  • *****
  • Posts: 542
Re: Raising an exception within a constructor after using self?
« Reply #17 on: October 30, 2025, 06:51:25 am »
In fact, I now see that this behavior is common to all functions when exceptions occur. This code will never print 999:

Code: Pascal  [Select][+][-]
  1. program Project1;
  2. uses SysUtils;
  3.   function test: integer;
  4.   begin
  5.     result := 999;
  6.     raise Exception.Create('');
  7.   end;
  8. var
  9.   i: integer;
  10. begin
  11.   try
  12.     i := 123;
  13.     i := test;
  14.   except
  15.   end;
  16.   writeln(i);
  17. end.

creaothceann

  • Full Member
  • ***
  • Posts: 196
Re: Raising an exception within a constructor after using self?
« Reply #18 on: October 30, 2025, 09:30:04 am »
I don't throw an exception - a standard function does that. I'm actually handling it.
By using TFileStream you are introducting a source of exceptions into your program.

Exceptions were originally created to force programmers to handle errors, because often these programmers were sloppy and would not check function results.

Your ReadDefaultConfig disables that mechanism by trying to return NIL in case of errors. You are basically fighting against the compiler/language. (Also, if you run your program from the IDE and an exception occurs, the IDE pops up and the program is halted until you manually continue it.)


The hypothetical "ReadDefaultConfig" function should return a TStream, while SysUtils.FileOpen returns a file handle.
There's THandleStream that takes the handle in its constructor.

You can also open the file (e.g. with FileOpen) to prevent it from being deleted by someone / some program, then open it with TFileStream, then close the original handle with FileClose. (This might only work on Windows; on Unix/Linux you may have to use THandleStream.)


Well, as far as I know, calling a function creates a stack frame, and the function's result is passed through it. So, I assumed that when the function exits, it should still return something, which will be assigned to a variable.
The function never "exits" normally though, it's interrupted (the regular execution path has crashed). The exceptions mechanism (which is not cheap) then cleans up the stack.


The user isn't required to know this anyway, so the documentation should explain it.
I disagree though, anyone using exceptions (which includes anyone using TFileStream) should know/learn this.
(If you think the user isn't required to know this, why should they need an explanation in the documentation?)
« Last Edit: October 30, 2025, 12:58:19 pm by creaothceann »
And don't start an argument, I am right.

PascalDragon

  • Hero Member
  • *****
  • Posts: 6184
  • Compiler Developer
Re: Raising an exception within a constructor after using self?
« Reply #19 on: October 30, 2025, 09:22:55 pm »
Perhaps the compiler could be improved by assigning "nil" in this situation? I'm sure many users expect this.

No, because this is simply the behavior for any function or method in which an exception is raised - either directly or indirectly.

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.

Neither is in FPC.

x := SomeClass.Create is two separate things: object creation, and an assignment. The latter is skipped when the exception occurs.

Well, as far as I know, calling a function creates a stack frame, and the function's result is passed through it. So, I assumed that when the function exits, it should still return something, which will be assigned to a variable. It seems there are still more implementation details...

No, this is only the case for managed types which a class is not.

ASBzone

  • Hero Member
  • *****
  • Posts: 733
  • Automation leads to relaxation...
    • Free Console Utilities for Windows (and a few for Linux) from BrainWaveCC
Re: Raising an exception within a constructor after using self?
« Reply #20 on: October 31, 2025, 03:21:33 pm »
The exceptions mechanism (which is not cheap) then cleans up the stack.

Thanks for this link.  That was a very good read.
-ASB: https://www.BrainWaveCC.com/

Lazarus v4.3.0.0 (bcf314a670) / FreePascal v3.2.3-46-g77716a79dc (aka fixes)
(Windows 64-bit install w/Win32 and Linux on ARM and x64 cross-compilers via FpcUpDeluxe)

My Systems: Windows 10/11 Pro x64 (Current)

BeniBela

  • Hero Member
  • *****
  • Posts: 947
    • homepage
Re: Raising an exception within a constructor after using self?
« Reply #21 on: November 05, 2025, 12:14:19 am »

The exceptions mechanism (which is not cheap) then cleans up the stack.

But fpc exceptions work differently. They are not zero-cost; they slow down all possibly catching functions even if there's no exception. However, they're likely faster if an exception does occur.

Khrys

  • Sr. Member
  • ****
  • Posts: 342
Re: Raising an exception within a constructor after using self?
« Reply #22 on: November 05, 2025, 07:14:37 am »

The exceptions mechanism (which is not cheap) then cleans up the stack.

But fpc exceptions work differently. They are not zero-cost; they slow down all possibly catching functions even if there's no exception. However, they're likely faster if an exception does occur.

I also used to think that, but FPC exception handling is platform-dependent>
  • Linux: Uses  fpc_pushexceptaddr / fpc_setjmp  to set up a handler in thread-local storage (storing a context record on the stack) and  fpc_popaddrstack  to tear it down
  • Windows (32-bit): Uses 32-bit SEH, which uses a few assembly instructions to set up a handler in thread-local storage and a few more to tear it down
  • Windows (64-bit): Uses 64-bit SEH, which does implement zero-cost exceptions on the happy path at the cost of making it very expensive to catch exceptions (using  RtlUnwindEx  etc.)
Using exceptions for control flow is probably cheapest on 32-bit Windows and most expensive on 64-bit Windows (with Linux somewhere in between), but please correct me if I'm wrong!



Edit: Added link to a detailed blog post discussing all variants of exception handling provided by Windows
« Last Edit: November 05, 2025, 07:17:49 am by Khrys »

Thaddy

  • Hero Member
  • *****
  • Posts: 18304
  • Here stood a man who saw the Elbe and jumped it.
Re: Raising an exception within a constructor after using self?
« Reply #23 on: November 05, 2025, 08:42:24 am »
Using exceptions for control flow is
a hanging offence.
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

DelphiFreak

  • Sr. Member
  • ****
  • Posts: 258
    • Fresh sound.
Re: Raising an exception within a constructor after using self?
« Reply #24 on: November 05, 2025, 08:54:31 am »
Here my input:

Avoid logic in constructors. Just use it to initzialize some member variables.
Avoid "raise" in your code. It's worse than "goto" from the old days. "raise" means "goto somewhere and hope someone cares".
Linux Mint 22, Lazarus 4.0, Windows 11, Delphi 12 Athens, Delphi 13 Florence

creaothceann

  • Full Member
  • ***
  • Posts: 196
Re: Raising an exception within a constructor after using self?
« Reply #25 on: November 05, 2025, 09:00:31 am »
I'd say raise and Assert are excellent tools for detecting and reporting bugs in the program.
And don't start an argument, I am right.

Thaddy

  • Hero Member
  • *****
  • Posts: 18304
  • Here stood a man who saw the Elbe and jumped it.
Re: Raising an exception within a constructor after using self?
« Reply #26 on: November 05, 2025, 10:35:28 am »
What many people forget is that when an exception occurs inside the constructor, the destructor is called anyway:
Code: Pascal  [Select][+][-]
  1. program Project1;
  2. type
  3.   TMyClass= class
  4.   private
  5.     i,y:integer;
  6.   public
  7.     constructor create;
  8.     destructor destroy;override;
  9.   end;
  10.  
  11. constructor TMyClass.create;
  12. begin
  13.   inherited;
  14.   i := 0;
  15.   y := 100 div i;// force exception
  16. end;
  17.  
  18. destructor TMyClass.destroy;
  19. begin
  20.   writeln('I got called anyway');
  21.   readln;
  22.   inherited destroy;
  23. end;
  24.  
  25. var
  26.   T:TMyclass;
  27. begin
  28.   T:= TMyClass.Create;
  29.   readln;
  30.   // No free, but destructor is called because of exception in constructor.
  31. end.
This is Delphi compatible.
« Last Edit: November 05, 2025, 10:43:30 am by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

DelphiFreak

  • Sr. Member
  • ****
  • Posts: 258
    • Fresh sound.
Re: Raising an exception within a constructor after using self?
« Reply #27 on: November 05, 2025, 01:27:29 pm »
The reason why I suggest not to put logic into constructor's (which may fail or raise execptions) is visible this example:

Assume someone has put logic into contructor of TSomeOtherClassA.create which raises an exception.

Code: Pascal  [Select][+][-]
  1. program Project1;
  2. type
  3.   TMyClass= class
  4.   private
  5.     FSomeOtherClassA:TSomeOtherClassA;
  6.     FSomeOtherClassB:TSomeOtherClassB;
  7.     i,y:integer;
  8.   public
  9.     constructor create;
  10.     destructor destroy;override;
  11.   end;
  12.  
  13. constructor TMyClass.create;
  14. begin
  15.   inherited;
  16.   FSomeOtherClassA:=TSomeOtherClassA.create;
  17.   FSomeOtherClassB:=TSomeOtherClassB.create;
  18. end;
  19.  
  20. destructor TMyClass.destroy;
  21. begin
  22.   FSomeOtherClassB.free;
  23.   FSomeOtherClassA.free;
  24.   inherited destroy;
  25. end;
  26.  
  27. var
  28.   T:TMyclass;
  29. begin
  30.   T:= TMyClass.Create;
  31.   readln;
  32.   // No free, but destructor is called because of exception in constructor.
  33. end.
Linux Mint 22, Lazarus 4.0, Windows 11, Delphi 12 Athens, Delphi 13 Florence

n7800

  • Hero Member
  • *****
  • Posts: 542
Re: Raising an exception within a constructor after using self?
« Reply #28 on: November 05, 2025, 09:50:42 pm »
I don't throw an exception - a standard function does that. I'm actually handling it.
By using TFileStream you are introducting a source of exceptions into your program.

Exceptions were originally created to force programmers to handle errors, because often these programmers were sloppy and would not check function results.

Your ReadDefaultConfig disables that mechanism by trying to return NIL in case of errors. You are basically fighting against the compiler/language.

Thanks for both links, they were really interesting!

But as I said, that was just a hypothetical example. I'm not sure it's possible to replace any code with exception-free code, including your own code or third-party units/packages.

Here my input:

Avoid logic in constructors. Just use it to initzialize some member variables.
Avoid "raise" in your code. It's worse than "goto" from the old days. "raise" means "goto somewhere and hope someone cares".

I want to reiterate that exceptions can be thrown indirectly due to usage within methods that call them, or any other function from a unit.

Furthermore, exceptions are thrown by the compiler (RTL?) even during division by zero, floating-point operations, and some other "basic" operations.

I'd say raise and Assert are excellent tools for detecting and reporting bugs in the program.

I'm not a fan of exceptions, but I must agree that they are a very effective way to detect errors.

I think our goal as developers is to learn how to create them rationally (not everywhere) and handle them correctly (without leaks and with proper initialization).

n7800

  • Hero Member
  • *****
  • Posts: 542
Re: Raising an exception within a constructor after using self?
« Reply #29 on: November 05, 2025, 09:55:41 pm »
By the way, regarding the performance of exceptions, I saw in the Wiki a trick with moving the exception call to a separate procedure, and I actually encountered it in code (for example, in the LCL):

https://wiki.freepascal.org/Avoiding_implicit_try_finally_section

 

TinyPortal © 2005-2018