2009-02-18 4 views
13

Ich habe eine delphi Funktion, die eine TStringList zurückgibt, aber wenn ich einen Wert zurückgeben und versuchen, es zu benutzen ich einen Fehler Zugriffsverletzung dhWie gebe ich ein Objekt aus einer Funktion in Delphi zurück, ohne Zugriffsverletzung zu verursachen?

myStringList := FuncStringList(); 
myStringList.Items.Count // <-- This causes an access violation 

// function FuncStringList 
function FuncStringList:TStringList; 
var 
    vStrList:TStringList; 
begin 

    vStrList := TStringList.Create; 
    ... 
    // Fill the vStrList 

    Result := vStrList 
    vStrList.Free; //<- when i free here, this function will cause AccessViolation 
end; 

bekommen Wie kann ich die TStringList und immer noch frei, um es in der lokalen Rück Funktion?

Antwort

28

Wie Smasher sagte, Sie können es nicht befreien; Der Code, der die Funktion aufruft, die das Objekt zurückgibt, ist dafür verantwortlich, sie zu zerstören.

Dies ist übrigens schlechtes Code-Design, da es Verwirrung darüber schafft, wer zuweist und freigibt. Ein wesentlich besserer Weg wäre es, wenn der Aufrufer das Objekt erstellt und an die Funktion weiterleitet. Auf diese Weise befreit der Code, der ihn erstellt, ihn auch. Etwas wie folgt aus:

var 
    SL: TStringList; 
begin 
    SL := TStringList.Create; 
    try 
    ProcToFillStringList(SL); 
    //Do something with populated list 
    finally 
    SL.Free; 
    end; 
end; 

// Note I've made the parameter a TStrings and not a TStringList. This allows 
// passing a TMemo.Lines or a TListBox or TComboBox Items as well. 
procedure ProcToFillStringList(const SList: TStrings); 
    // Do whatever populates the list with SList.Add() 
end; 

Jetzt gibt es keine Verwirrung darüber, wer das tut, was - der gleiche Code, der das Objekt erstellt für die Freigabe verantwortlich ist. Und der Code, IMO, ist viel klarer zu lesen und zu pflegen.

+0

Vorsicht. Da stack-allocated Variablen nicht auf 0 initialisiert werden, und assigned() nur auf <> nil prüft, wird die Assertion * nicht * ausgelöst, wenn sie vor dem Aufruf der Funktion nicht erzeugt wird. –

+0

Guter Fang, Mason. Ich werde das in meinem Post korrigieren. –

27

Wie kann ich die TStringList zurückgeben und immer noch in der lokalen Funktion frei?

Sie können nicht. Wenn Sie es in der lokalen Funktion freigeben, können Sie den Rückgabewert nicht verwenden. Ergebnis und vStrList verweisen auf dasselbe TStringList-Objekt im Speicher. TStringList ist eine Klasse und

Result := vStrList 

kopiert daher nicht die String-Liste, sondern kopiert nur die Referenz.

Anstatt also sollten Sie die String-Liste in der anrufenden Kontext frei, nachdem Sie die Arbeit mit ihm fertig oder die String-Liste als Parameter an die Funktion wie dieser Code

procedure FuncStringList (StringList : TStringList); 

und lassen Sie die Berufung passieren Erstellen und befreien Sie die String-Liste. Wie aus den anderen Antworten hervorgeht, ist dies der vorzuziehende Weg, da dies die Eigentumsverhältnisse sehr deutlich macht.

+1

+1, aber man sollte IMHO eine Notiz, dass der zweite Weg (haben die Anrufer übergeben Sie die String-Liste als Referenz) ist die Standard-Delphi-Idiom, und dass die erste ist wirklich nur für Werksfunktionen und die hinzufügen mögen. – mghie

+0

Sie haben absolut Recht und ich habe meine Antwort so bearbeitet, wie Sie es vorgeschlagen haben. Vielen Dank! – jpfollenius

0

Lassen Sie das Objekt nicht frei, bevor Sie die Methoden auf ihm ausgeführt haben. Sie rufen gerade die Count-Methode für ein zerstörtes Objekt auf, daher der Fehler.

Warum erstellen Sie nicht stattdessen die String-Liste in der aufrufenden Funktion, und übergeben Sie ihre Referenz auf die Methode, die es füllt? Oder machen Sie die String-Liste zu einem Mitglied einer Klasse und befreien Sie sie, wenn Sie die Klasse freigeben, die sie besitzt?

2

Sie können einfach nichts freigeben und dann erwarten, dass Sie später darauf verweisen. Das ist der falsche Weg. Sie haben zwei grundlegende Optionen:

  • nicht kostenlos anrufen, und der Anrufer verantwortlich machen für
  • des Objekts Entsorgung Lassen Sie den Anrufer in einem Objekt übergeben, so dass es für beide erstellen und Free
verantwortlich ist

Die erste Option scheint einfacher zu sein, hält die Schnittstelle zu der Funktion kleiner usw. Die zweite Option macht die Verwendung weniger fehleranfällig, weil es für den Aufrufer intuitiv ist, dass es für die Verwaltung des Objekts zuständig ist.

6

Einfache Antwort: Sie können nicht. Warum versuchst du? Liegt es daran, dass Sie gelernt haben, dass Sie jedes Objekt, das Sie erstellen, in derselben Funktion freigeben müssen, in der sie erstellt wurden? Das ist im Allgemeinen richtig, aber nicht immer, und dies ist eine der Ausnahmen von der Regel. Eine bessere Möglichkeit ist es, dass jedes Objekt von seinem Besitzer freigegeben werden muss.

Wenn Sie eine Funktion haben, die ein Objekt wie dieses generiert, aber dann an eine andere Funktion weitergibt, übernimmt es nicht das Eigentum an dem Objekt. Entfernen Sie den Aufruf, um ihn freizugeben und zu dokumentieren, damit Sie (und alle anderen, die diese Funktion verwenden) erkennen, dass ein neues Objekt erstellt wird, an dem der Code, der es aufruft, das Eigentum übernehmen soll.

+3

Genau meine Ansicht. Ich gebe Funktionen wie diesen einen Namen, der mit Create beginnt, daher hat der Aufrufer den Hinweis, dass er das zurückgegebene Objekt ähnlich wie das Ergebnis eines Konstruktoraufrufs behandeln muss. –

0

Eine andere Möglichkeit besteht darin, ein dynamisches Array anstelle einer TStringList zu verwenden. Da Arrays Referenz gezählt werden, müssen Sie sich nie darum sorgen, sie zu befreien.

+0

Nicht wirklich hilfreich, da der meiste vorhandene Code mit TStrings funktioniert. Oder gibt es in den letzten Delphi-Versionen eine Möglichkeit, einem TStrings-Objekt ein dynamisches Array von Strings zuzuordnen? Um es zu sortieren? Es würde auch die Objects-Eigenschaft fehlen. Es gibt genug Gründe, bei TStrings zu bleiben. – mghie

+0

Ja, aber stattdessen müssen Sie sich darum sorgen, die Länge des Arrays manuell anzupassen. Wenn Sie dies naiv genug implementieren, werden Sie mit einem Code-Code mit sehr schlechter Leistung enden. Ich bevorzuge wirklich TStringList. – jpfollenius

+0

Nun, es hängt wirklich davon ab, was er damit macht - ich habe es nur als eine Möglichkeit herausgestellt. Alles was ich sehe ist eine Ellipse. – smo

0

Eine Richtlinie, die ich in solchen Situationen habe, besteht darin, den Inhalt der Zeichenfolgeliste durch die text -Eigenschaft zu übergeben und die zurückgegebene Zeichenfolge einfach an die Funktion zu übergeben. Auf diese Weise muss nicht darüber diskutiert werden, wer wen veröffentlicht. Natürlich müssen Sie ein wenig mehr programmieren, aber es ist sicherer. Das Beispiel ist eine Anpassung der eigenen Ken White:

var 
    SL: TStringList; 
    Aux: String; 
begin 
    SL := TStringList.Create; 
    try 
    SL.Text := ProcToFillStringList; 
    //Do something with populated list 
    finally 
    SL.Free; 
    end; 
end; 

// It receives a default param, in the case you have to deal with 
// StringList with some previous content  
function ProcToFillStringList(SListContent: String = ''):String; 
// Do the stuff you need to do with the content 
end; 

Eine Ausnahme ist, wenn alles, was Sie haben das Objekt ist, und es gibt keine Möglichkeit, den Inhalt auf sie über eine sichere Art (in diesem Fall, Streicher) abrufen ; dann folge ich Ken Whites Idee.

2

Einfache Antwort (mit Beispielen):

Wenn Sie

Ergebnis tun: = vStrList

Sie zuweisen vStrList Ergebnis. In diesem Moment sind vStrList und Result SIND DAS GLEICHE! Also, in der nächsten Zeile des Codes, wenn Sie vStrList freigeben, geben Sie auch Ergebnis frei (gut, das ist nicht technisch genau, aber ich habe es verwendet, um die Erklärung einfach zu halten). Aus diesem Grund erhalten Sie einen AV, wenn Sie versuchen, das Ergebnis der Funktion zu verwenden. Das Ergebnis wurde zerstört, als Sie vStrList freigegeben haben.

So wird dies Ihr Problem lösen:

function FuncStringList:TStringList; 
begin 
    Result := TStringList.Create; 
    // Do stuff with Result 
    // never free (here, in this function) the Result 
end; 

Der Anrufer von FuncStringList wird befreien "Ergebnis" HAT.

Sie nennen es wie folgt aus:

myStringList := FuncStringList; 
try 
    myStringList.Items.Count      
finally 
    freeandnil(myStringList); <------------- NOW you can free "Result" 
end; 

.

Ein weiteres Beispiel:

function makelist: tstringlist; 
begin 
    result := tstringlist.create; 
    result.add('1'); 
    result.add('2'); 
end; 

procedure TForm1.Button_GOOD_Click(Sender: TObject); 
var list : tstringlist; 
begin 
    list := makelist; 
    DoStuff(list); 
    list.free;  //ok 
end; 

procedure TForm1.Button_BAD_Click(Sender: TObject); 
begin 
    listbox1.items.Assign(makelist); // <---- memory leak here because you forgot to free 
end; 

habe ich diese Notiz hier, bevor jemand beginnt 'Picking' auf meiner Erklärung. Ich habe einige "Abkürzungen" in meiner Erklärung verwendet, um zu vermeiden, dass komplexe Konzepte (wie Zeigerzuweisung) die Dinge sehr einfach halten. @gath hat eine grundlegende Frage gestellt, was bedeutet, dass er immer noch die Grundlagen der Programmierung lernt.

0

beide beziehen sich auf den gleichen Speicher, wenn Sie es freigeben beide werden befreit .......

0

Entweder als Out-Variable.

function GetList(Parameter1: string; out ResultList: TStringList): boolean; 
begin 
    // either 
    if not Assigned(ResultList) then 
    raise Exception.Create('Out variable is not declared.'); 
    // or 
    Result := False; 
    if not Assigned(ResultList) then 
    Exit; 
    ResultList.Clear; 
    ResultList.Add('Line1'); 
    ResultList.Add('Line2'); 
    //... 
    Result := True; 
end; 

Oder als Zeichenfolge.

function GetList(Parameter1: string): string; 
var 
    slList: TStringList; 
begin 
    slList := TStringList.Create; 
    try 
    slList.Clear; 
    slList.Add('Line1'); 
    slList.Add('Line2'); 
    //... 
    Result := slList.Text; 
    finally 
    slList.Free; 
    end; 
end; 

.

procedure Main; 
var 
    slList: TStringList; 
begin 
    slList := TStringList.Create; 
    try 
    // either 
    GetList(Parameter1, slList); 
    // or 
    slList.Text := GetList(Parameter1); 
    // process slList... 
    finally 
    slList.Free; 
    end; 
end; 
Verwandte Themen