While tracing a nasty bug (?), I discovered the hard way that when an Assert is done in a constructor, and it fails, then the destructor (Destroy) is automatically called.
However, because the constructor failed, the object did not satisfy the class invariants. So, the destructor needs to be pessimistic about the state. Mine was not. Since the Assert in the constructor is there to protect against parameter values that do not statisfy the constructor's precondition, there are no additional dynamic veriables and objects created, and hence they need not be destroyed. How can the destructor know that it is called in such a state? Below is a simple example to illustrate the problem. My destructor assumed that the class invariants hold, and it uses some of the class methods to aid in tearing down the object. Except that the object does not exist if the destructor gets called because of the failed Assert in the constructor. A painful lesson in Design By Contract... Is there any place in the documentation, where one can find about this implicit call of the destructor? Should it be added? The program below writes FAIL: Raised unexpected exception Access violation which is raised in the destructor, after the EAssertionFailed excpetion is raised in the constructor. When uncommenting the if statement in the destructor, it writes: OK: Raised EAssertionFailedTRing.Create pre failed: n = -1 (assert_in_constructor.pas, line 44) In that case, one needs to rely on the fact that fields in a class get initialized in a predefined way, e.g. pointers are set to nil. Best regards, Tom Verhoeff -- program Assert_in_constructor; {$Mode Delphi} {$Assertions on} uses SysUtils, Classes; type PCell = ^ TCell; TCell = record // singly linked list of integers FData: Integer; FNext: PCell; end; TRing = class ( TObject ) private FList: PCell; // the elements in the ring // Invariants: // NonEmpty: FList <> nil; // IsRing: repeated application of ^.FNext to FList leads to FList, // i.e., the last cell points to the head cell // Unique: all p^.FData for p: PCell appearing in FList are different public constructor Create ( n: Integer ); { pre: 0 < n; post: ring consists of 0 through n-1 } function IsSingleton: Boolean; function Current: Integer; procedure Step; procedure RemoveSuccessor; { pre: not IsSingleton } destructor Destroy; override; end; constructor TRing.Create ( n: Integer ); var i: Integer; q: PCell; // points to last cell, with FData = i-1 begin inherited Create; Assert ( 0 < n, Format ( 'TRing.Create pre failed: n = %D', [ n ] ) ); New(FList); q := FList; q^.FData := 0; i := 1; while i <> n do begin New ( q^.FNext ); q := q^.FNext; q^.FData := i; Inc ( i ); end; q^.FNext := FList; // ring closed end; function TRing.IsSingleton: Boolean; begin Result := FList^.FNext = FList; end; function TRing.Current: Integer; begin Result := FList^.FData; end; procedure TRing.Step; begin FList := FList^.FNext; end; procedure TRing.RemoveSuccessor; var q: PCell; // cell to be removed begin q := FList^.FNext; FList^.FNext := q^.FNext; // q unlinked Dispose ( q ); end; destructor TRing.Destroy; begin //if FList <> nil then while not IsSingleton do RemoveSuccessor; Dispose ( FList ); inherited Destroy; end; var v: TRing; begin try v := TRing.Create ( -1 ); writeln ( 'FAIL: Should have raised EAssertionFailed' ); except on e : EAssertionFailed do begin writeln ( 'OK: Raised EAssertionFailed' + e.Message ) end; on e : Exception do begin writeln ( 'FAIL: Raised unexpected exception ' + e.Message ) end else writeln ( 'FAIL: Raised an unrecognized exception' ) end; { try } end. _______________________________________________ fpc-pascal maillist - fpc-pascal@lists.freepascal.org http://lists.freepascal.org/mailman/listinfo/fpc-pascal