Think twice before raising exception in constructor

19

Recently prolific Delphi writer Nick Hodges declared a war on the use of nil pointers. It is arguable whether nil pointers usage is bad or not, but the point is the “Guard Pattern” proposed is really an antipattern. Here is a simple demo:

program Project11;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes;

type
  TMyClass = class
  private
    FList: TStringList;
  public
    constructor Create(AList: TStringList);
    destructor Destroy; override;
  end;

constructor TMyClass.Create(AList: TStringList);
begin
  if AList = nil then
  begin
    raise Exception.Create('Don''t you dare pass me a nil reference!);
  end;
  FList:= AList;
  FList.Add('TMyClass instance created');
end;

destructor TMyClass.Destroy;
begin
  FList.Add('TMyClass instance destroyed');
  inherited Destroy;
end;

procedure Test;
begin
  TMyClass.Create(nil);
end;

begin
  try
    Test;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

If you understand how exceptions raised in a constructor work you see that the above code raises 2 exceptions: the first is the “wanted” exception in the constructor, the second is the “unwanted” access violation in the destructor.

The moral is: if you raise an exception in a constructor be very careful when writing the destructor, cause you need to treat a case of improperly initialized instance.

How to get size of adjacent record’s fields correctly?

1

Suppose we have two record types TRecA and TRecB declared as shown below; in a sense the TRecB type is ‘inherited’ from TRecA.

The goal is to write a general method which clears all TRecB fields which are not ‘inherited’ from TRecA. The code below propose 3 candidate solutions TRecB.ClearX:

    type
      PRecA = ^TRecA;
      TRecA = record
        FieldA1: LongWord;
        FieldA2: Byte;
      end;
    
      PRecB = ^TRecB;
      TRecB = record
        FieldA1: LongWord;
        FieldA2: Byte;
    
        FieldB1: Word;
        FieldB2: LongInt;
        FieldB3: Byte;
        Sentinel: record end;
    
        procedure Clear1;
        procedure Clear2;
        procedure Clear3;
      end;
    
    { TRecB }
    
    procedure TRecB.Clear1;
    begin
      FillChar(FieldB1, SizeOf(TRecB) - SizeOf(TRecA), 0);
    end;
    
    procedure TRecB.Clear2;
    begin
      FillChar(FieldB1,
        SizeOf(TRecB) - Integer(@PRecB(nil)^.FieldB1), 0);
    end;
    
    procedure TRecB.Clear3;
    begin
      FillChar(FieldB1,
        Integer(@PRecB(nil)^.Sentinel) - Integer(@PRecB(nil)^.FieldB1), 0);
    end;

At a first glance all three ClearX methods do the same but they are not. The Clear1 solution is simply wrong while Clear2 and Clear3 are different.

To understand why it is so one need to understand how types’ sizes and alignments are calculated.

Since the 80-bit Extended was kicked out from Delphi, every type’s size is multiple of the type’s alignment. TRecA type has alignment 4 (which is the LongWord type alignment, the biggest alignment among TRecA fields), and the size of the 2nd field is less than 4, so SizeOf(TRecA) = 8.

The alignment of Word type is 2, thus the offset of FieldB1 in TRecB type is 6. That is why Clear1 solution is wrong: it may work correctly for some sets of FieldBX fields, but generally it is incorrect.

Applying the alignment rules to TRecB type we obtain the next layout:

      TRecBLayout = record
        FieldA1: LongWord;
        FieldA2: Byte;
        Filler1: Byte;  
        FieldB1: Word;
        FieldB2: LongInt;
        FieldB3: Byte;
        Filler2: Byte;  
        Filler3: Byte;  
        Filler4: Byte;
      end;  

Filler bytes at the end are required to make TRecB size multiple of TRecB alignment (=4)

So SizeOf(TRecB) = 16, but OffsetOf(TRecB.Sentinel) = 13 (unfortunately Delphi compiler does not support OffsetOf() built-in). That is why Clear2 and Clear3 methods are different: only Clear3 does exactly what is asked – clears ‘non-inherited’ fields; Clear2 also clears filler bytes at the record’s end.