2009-02-18 16 views
13

Ho una funzione di Delphi che restituisce un TStringList, ma quando torno un valore e provare a usarlo ottengo un errore di violazione di accesso cioèCome posso restituire un oggetto da una funzione in Delphi senza causare la violazione di accesso?

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; 

Come posso restituire il TStringList e ancora libero nel locale funzione?

risposta

28

Come ha detto Smasher, non è possibile liberarlo; il codice che chiama la funzione che restituisce l'oggetto è responsabile della sua distruzione.

Questo è un cattivo progetto di codice, a proposito, in quanto rende confuso su chi assegna e libera. Un modo molto migliore per farlo sarebbe avere il chiamante per creare l'oggetto e passarlo alla funzione. In questo modo, il codice che lo crea lo libera anche. Qualcosa di simile a questo:

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; 

Ora non c'è confusione su chi fa cosa - lo stesso codice che crea l'oggetto è responsabile di liberare esso. E il codice, IMO, è molto più chiaro da leggere e mantenere.

+0

Attenzione. Poiché le variabili allocate nello stack non sono inizializzate su 0, e assegnate() controlla solo per <> nil, non riuscendo a crearlo prima di chiamare la funzione * non * attiverà l'asserzione. –

+0

Buona presa, Mason. Lo correggerò nel mio post. –

27

Come posso restituire TStringList e comunque liberarlo nella funzione locale?

Non è possibile. Se lo si libera nella funzione locale, non è possibile utilizzare il valore restituito. Risultato e vStrList puntano allo stesso oggetto TStringList in memoria. TStringList è una classe e

Result := vStrList 

non copia quindi l'elenco di stringhe, ma copia solo il riferimento.

Così, invece si dovrebbe liberare la lista stringa nel contesto di chiamata dopo aver finito a lavorare con esso o passare la lista stringa come un parametro alla funzione come questa

procedure FuncStringList (StringList : TStringList); 

e lasciare che il codice chiamante creare e liberare la lista delle stringhe. Come sottolineato dalle altre risposte, questo è il modo preferibile, poiché rende la proprietà molto chiara.

+1

+1, ma si dovrebbe IMHO aggiungere una nota che il secondo modo (hanno il chiamante passare la lista di stringhe come riferimento) è il linguaggio standard di Delphi, e che il primo è veramente adatto solo per le funzioni di fabbrica e la piace. – mghie

+0

Hai perfettamente ragione e ho modificato la mia risposta come l'hai proposta tu. Grazie! – jpfollenius

0

Non liberare l'oggetto prima di aver terminato di invocare i metodi su di esso. Stai attualmente invocando il metodo Count su un oggetto distrutto, quindi l'errore.

Perché non creare l'elenco di stringhe nella funzione di chiamata e passare il suo riferimento al metodo che lo riempie? Oppure rendere la lista delle stringhe un membro di una classe e liberarla quando si libera la classe che la possiede?

2

Semplicemente non è possibile liberare qualcosa e quindi aspettarsi di fare riferimento più tardi. Questo è il modo sbagliato. Sono disponibili due opzioni di base:

  • non chiamare gratis, e rendere il chiamante responsabile per lo smaltimento dell'oggetto
  • hanno passaggio il chiamante in un oggetto in modo che sia responsabile sia Creare e Free

La prima opzione sembra più semplice, mantiene l'interfaccia della funzione più piccola, ecc. La seconda opzione rende l'utilizzo meno soggetto a errori perché è intuitivo per il chiamante che è responsabile della gestione dell'oggetto.

6

Risposta semplice: non è possibile. Perché stai provando? È perché hai imparato che devi liberare ogni oggetto che crei nella stessa funzione in cui sono creati? Questo è generalmente corretto, ma non sempre, e questa è una delle eccezioni alla regola. Un modo migliore per dirlo è che ogni oggetto deve essere liberato dal suo proprietario.

Se si dispone di una funzione che genera un oggetto, come questo, ma poi lo passa ad un'altra funzione, non assume la proprietà dell'oggetto. Rimuovi la chiamata per liberarla e documentala, così tu (e chiunque altro usi questa funzione) realizzerai che crea un nuovo oggetto di cui il codice che lo chiama deve diventare proprietario.

+3

Esattamente la mia vista. Diamo a funzioni come questo un nome che inizia con Crea, quindi il chiamante ha la certezza che deve gestire l'oggetto restituito simile al risultato di una chiamata di costruttore. –

0

Un'altra possibilità è utilizzare una matrice dinamica anziché una TStringList. Poiché gli array sono conteggiati, non dovrai mai preoccuparti di liberarlo.

+0

Non molto utile, poiché la maggior parte del codice esistente funziona con TStrings. O c'è un modo nelle recenti versioni di Delphi per assegnare un array dinamico di stringhe ad un oggetto TStrings? Per ordinarlo? Manca anche la proprietà Objects. Ci sono sufficienti ragioni per stare con TStrings. – mghie

+0

Sì, ma invece devi preoccuparti di regolare manualmente la lunghezza dell'array. Se implementi abbastanza questo ingenuo, finirai con uno snippet di codice molto scarso. Preferisco davvero usare TStringList. – jpfollenius

+0

Beh, in realtà dipende da cosa ci sta facendo - l'ho appena indicato come una possibilità. Tutto quello che vedo sono le ellissi. – smo

0

Una politica che ho in tali situazioni è quella di passare il contenuto della lista di stringhe attraverso la proprietà text e semplicemente passare la stringa restituita alla funzione. In questo modo non c'è bisogno di discutere chi rilascia chi. Certo, devi fare un po 'più di programmazione, ma è più sicuro. L'esempio è un adattamento di uno del Ken Bianco:

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; 

Un'eccezione è quando hai a disposizione solo l'oggetto e non c'è modo di recuperare il contenuto di esso attraverso un tipo di sicurezza (in questo caso, le stringhe) ; poi seguo l'idea di Ken White.

2

Risposta semplice (con esempi):

Quando si esegue

Risultato: = vStrList

si assegna vStrList ai risultati. In questo momento vStrList e Result SONO LA STESSA COSA! Quindi, nella prossima riga di codice, quando si libera vStrList, si libera anche Result (beh, questo non è TECNICO, ma l'ho usato per mantenere la spiegazione semplice). Questo è il motivo per cui si ottiene un AV quando si tenta di utilizzare il risultato della funzione. Il risultato è stato distrutto quando hai liberato vStrList.

Quindi, questo risolverà il problema:

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

Il chiamante di FuncStringList dovrà liberare "Risultato".

si chiamano in questo modo:

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

.

Un altro esempio:

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; 

ho messo questa nota qui prima che qualcuno avrà inizio 'raccogliere' sulla mia spiegazione. Nella mia spiegazione ho usato alcune "scorciatoie" per evitare che concetti complessi (come l'assegnazione del puntatore) mantengano le cose molto semplici. @gath ha posto una domanda di base che significa che sta ancora imparando le basi della programmazione.

0

entrambi stanno facendo riferimento alla stessa memoria, se si libera entrambi verranno liberati .......

0

O come variabile Out.

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; 

O come stringa.

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; 
Problemi correlati