[fpc-pascal] If an Assert fails in constructor, then destructor called (?)
Tom Verhoeff
T.Verhoeff at tue.nl
Tue Jun 30 00:19:15 CEST 2009
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.
More information about the fpc-pascal
mailing list