Generisch hin, (un)typisiert her. Mir geht es einfach nur um eine doppelt verkettete Liste. Allein bei deren Deklaration bekam ich schon zu Turbo-Pascal-Zeiten einen dicken Hals: Wie kann etwas sich selbst vollständig als echte Teilmenge enthalten? Das ist mit mathematischer Logik nicht faßbar, das gibt es n.m.W. nur bei unendlichen Mengen. Inzwischen weiß ich, wie das gemeint ist.
Warum brauchst du denn für eine doppelt verkettete Liste explizite Pointer?
Wenn man diesen Unrat wenigstens vernünftig debuggen könnte, ist aber leider Fehlanzeige.
Ich habe mal kurz eine doppelt verkettete Liste geschrieben, die kann ich auch sehr gut debuggen (wobei ich die geschrieben und einmal testweise gestartet habe und es lief
):
Delphi-Quellcode:
type
TDoubleLinkedListEntry<T> = class
private
var
FValue: T;
FPrevious: TDoubleLinkedListEntry<T>;
FNext: TDoubleLinkedListEntry<T>;
procedure SetValue(const Value: T);
procedure SetNext(const Value: TDoubleLinkedListEntry<T>);
procedure SetPrevious(const Value: TDoubleLinkedListEntry<T>);
public
constructor Create(const AValue: T; const APrevious, ANext: TDoubleLinkedListEntry<T>);
property Value: T read FValue write SetValue;
property Previous: TDoubleLinkedListEntry<T> read FPrevious write SetPrevious;
property Next: TDoubleLinkedListEntry<T> read FNext write SetNext;
end;
TDoubleLinkedList<T> = class
private
var
FHead, FTail: TDoubleLinkedListEntry<T>;
type
TListEnumerator = class
private
var
FFirst: Boolean;
FCurrent: TDoubleLinkedListEntry<T>;
public
constructor Create(AList: TDoubleLinkedList<T>);
property Current: TDoubleLinkedListEntry<T> read FCurrent;
function MoveNext: Boolean;
end;
public
destructor Destroy; override;
procedure Append(const Value: T);
procedure Delete(const Value: T);
procedure Remove(const Value: TDoubleLinkedListEntry<T>);
function GetEnumerator: TListEnumerator;
property Head: TDoubleLinkedListEntry<T> read FHead;
property Tail: TDoubleLinkedListEntry<T> read FTail;
end;
{ TDoubleLinkedListEntry<T> }
constructor TDoubleLinkedListEntry<T>.Create(const AValue: T; const APrevious, ANext: TDoubleLinkedListEntry<T>);
begin
FValue := AValue;
FPrevious := APrevious;
FNext := ANext;
end;
procedure TDoubleLinkedListEntry<T>.SetNext(const Value: TDoubleLinkedListEntry<T>);
begin
FNext := Value;
end;
procedure TDoubleLinkedListEntry<T>.SetPrevious(const Value: TDoubleLinkedListEntry<T>);
begin
FPrevious := Value;
end;
procedure TDoubleLinkedListEntry<T>.SetValue(const Value: T);
begin
FValue := Value;
end;
{ TDoubleLinkedList<T> }
destructor TDoubleLinkedList<T>.Destroy;
var
CurrentEntry: TDoubleLinkedListEntry<T>;
begin
if Assigned(FHead) then
begin
CurrentEntry := FHead;
while Assigned(CurrentEntry.Next) do
begin
CurrentEntry := CurrentEntry.Next;
CurrentEntry.Previous.Free;
end;
FTail.Free;
end;
inherited;
end;
function TDoubleLinkedList<T>.GetEnumerator: TListEnumerator;
begin
Result := TListEnumerator.Create(Self);
end;
procedure TDoubleLinkedList<T>.Append(const Value: T);
begin
if Assigned(FTail) then
begin
FTail.Next := TDoubleLinkedListEntry<T>.Create(Value, FTail, nil);
FTail.Next.Previous := FTail;
FTail := FTail.Next;
end
else
begin
FTail := TDoubleLinkedListEntry<T>.Create(Value, FTail, nil);
FHead := FTail;
end;
end;
procedure TDoubleLinkedList<T>.Remove(const Value: TDoubleLinkedListEntry<T>);
begin
if Assigned(Value.Previous) then
Value.Previous.Next := Value.Next;
if Assigned(Value.Next) then
Value.Next.Previous := Value.Previous;
end;
procedure TDoubleLinkedList<T>.Delete(const Value: T);
var
CurrentEntry: TDoubleLinkedListEntry<T>;
begin
CurrentEntry := FHead;
while Assigned(CurrentEntry) do
if TComparer<T>.Default.Compare(CurrentEntry.Value, Value) = 0 then
begin
Remove(CurrentEntry);
CurrentEntry.Free;
Break;
end
else
CurrentEntry := CurrentEntry.Next;
end;
{ TDoubleLinkedList<T>.TListEnumerator }
constructor TDoubleLinkedList<T>.TListEnumerator.Create(AList: TDoubleLinkedList<T>);
begin
FFirst := True;
FCurrent := AList.Head;
end;
function TDoubleLinkedList<T>.TListEnumerator.MoveNext: Boolean;
begin
Result := Assigned(FCurrent) and (Assigned(FCurrent.Next) or FFirst);
if Result and not FFirst then
FCurrent := FCurrent.Next;
FFirst := False;
end;
Benutzung:
Delphi-Quellcode:
var
Test: TDoubleLinkedList<Integer>;
CurrentEntry: TDoubleLinkedListEntry<Integer>;
begin
Test := TDoubleLinkedList<Integer>.Create;
try
Test.Append(100);
Test.Append(300);
Test.Append(200);
for CurrentEntry in Test do
ShowMessage(IntToStr(CurrentEntry.Value)); // 100, 300, 200
Test.Delete(300);
for CurrentEntry in Test do
ShowMessage(IntToStr(CurrentEntry.Value)); // 100, 200
finally
Test.Free;
end;
end;
Und debuggen kann ich das auch problemlos:
Sicher, explizite Zeiger braucht man an manchen Stellen natürlich. Solange man das ganze durchdacht strukturiert machen die aber auch nicht mehr Probleme als andere Sprachbestandteile.
Ein Beispiel, das ich beruflich vor einer Weile brauchte, war ein generischer Ringpuffer. Da dort auch z.B. Records hineingespeichert werden können sollten, brauchte ich einen Pointer auf die gespeicherten Daten um diese verändern zu können. Dies wird leider nicht komplett unterstützt, aber vom Prinzip her sah das am Ende so aus:
Delphi-Quellcode:
TCircularBuffer<T> = class
public
type
TBufferPointer = ^T;
strict private
var
FElements: TArray<T>;
// ...
public
type
TBufferEnumerator = class(TEnumerator<TCircularBuffer<T>.TBufferPointer>)
// ...
public
constructor Create(ABuffer: TCircularBuffer<T>);
property Current: TCircularBuffer<T>.TBufferPointer read GetCurrent;
function MoveNext: Boolean;
end;
function GetEnumerator: TBufferEnumerator; reintroduce;
// ...
end;
Ohne Generics und den Pointer in Kombination wäre das gar nicht möglich das typsicher so umzusetzen. Aber trotzdem ist das ganze immer noch typsicher.