software - delphi technologies
¿Cómo devuelvo un objeto de una función en Delphi sin causar una Infracción de acceso? (10)
¿Cómo puedo devolver TStringList y todavía liberarlo en la función local?
No puedes. Si lo libera en la función local, no puede usar el valor de retorno. El resultado y vStrList apuntan al mismo objeto TStringList en la memoria. TStringList es una clase y
Result := vStrList
por lo tanto, no copia la lista de cadenas, sino que solo copia la referencia.
Por lo tanto, en su lugar, debe liberar la lista de cadenas en el contexto de llamada una vez que haya terminado de trabajar con ella o pasar la lista de cadenas como un parámetro para su función como esta
procedure FuncStringList (StringList : TStringList);
y permita que el código de llamada cree y libere la lista de cadenas. Como se señala en las otras respuestas, esta es la forma preferible, ya que hace que la propiedad sea muy clara.
Tengo una función delphi que devuelve una TStringList, pero cuando devuelvo un valor y trato de usarlo obtengo un error de infracción de acceso, es decir,
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;
¿Cómo puedo devolver TStringList y todavía liberarlo en la función local?
Como dijo Smasher, no puedes liberarlo; el código que llama a la función que devuelve el objeto es responsable de destruirlo.
Por cierto, este es un diseño de código incorrecto, ya que confunde quién asigna y libera. Una forma mucho mejor de hacerlo sería hacer que la persona que llama cree el objeto y pasarlo a la función. De esa forma, el código que lo crea también lo libera. Algo como esto:
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;
Ahora no hay confusión sobre quién hace qué: el mismo código que crea el objeto es responsable de liberarlo. Y el código, IMO, es mucho más claro de leer y mantener.
No libere el objeto antes de que termine de invocar los métodos en él. Actualmente está invocando el método Count en un objeto destruido, de ahí el error.
¿Por qué no crea la lista de cadenas en la función de llamada y pasa su referencia al método que la llena? ¿O hacer que la lista de cuerdas sea miembro de una clase y liberarla cuando liberas la clase que lo posee?
Otra posibilidad es usar una matriz dinámica en lugar de una TStringList. Dado que las matrices son referencias contadas, nunca tendrá que preocuparse de liberarlas.
Respuesta simple: no puedes. ¿Por qué estás tratando de hacerlo? ¿Es porque has aprendido que necesitas liberar cada objeto que creas en la misma función en la que fueron creados? En general, es correcto, pero no siempre, y esta es una de las excepciones a la regla. Una mejor manera de decirlo es que cada objeto debe ser liberado por su propietario.
Si tiene una función que genera un objeto, como este, pero luego lo transfiere a otra función, no se apropia del objeto. Elimine la llamada para liberarla y documentarla, para que usted (y cualquier otra persona que use esta función) se dé cuenta de que crea un nuevo objeto que el código que lo llama debe apropiarse.
Simplemente no puedes liberar algo y luego esperar hacer referencia a él más tarde. Esa es la manera incorrecta. Tienes dos opciones básicas:
- No llame gratis, y haga que el que llama sea responsable de deshacerse del objeto
- Haga que la persona que llama pase un objeto para que sea responsable de crear y liberar
La primera opción parece más simple, mantiene la interfaz con la función más pequeña, etc. La segunda opción hace que el uso sea menos propenso a errores porque es intuitivo para la persona que llama que es responsable de administrar el objeto.
Una política que tengo con tales situaciones es pasar el contenido de la lista de cadenas a través de la propiedad de texto y simplemente pasar la cadena devuelta a la función. De esta forma no hay necesidad de discutir quién libera a quién. Por supuesto, tienes que hacer un poco más de codificación, pero es más seguro. El ejemplo es una adaptación del de 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;
Una excepción es cuando todo lo que tiene es el objeto y no hay forma de recuperar el contenido a través de un tipo seguro (en este caso, cadenas); entonces sigo la idea de Ken White.
ambos están refiriendo a la misma memoria, si la liberas, ambos serán liberados .......
Respuesta simple (con ejemplos):
Cuando tu lo hagas
Resultado: = vStrList
asigna vStrList a Resultado. ¡En este momento, vStrList y Result SON LO MISMO! Entonces, en la siguiente línea de código, cuando liberas vStrList, liberas también Result (bueno, esto no es TÉCNICAMENTE preciso, pero lo usé para mantener la explicación simple). Es por eso que obtienes un AV cuando intentas usar el resultado de la función. El resultado se destruyó cuando liberaste vStrList.
Por lo tanto, esto resolverá su problema:
function FuncStringList:TStringList;
begin
Result := TStringList.Create;
// Do stuff with Result
// never free (here, in this function) the Result
end;
La persona que llama de FuncStringList TENDRÁ que liberar el "resultado".
Lo llamas así:
myStringList := FuncStringList;
try
myStringList.Items.Count
finally
freeandnil(myStringList); <------------- NOW you can free "Result"
end;
.
Otro ejemplo:
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;
Puse esta nota aquí antes de que alguien empiece a ''elegir'' mi explicación. Usé algunos ''accesos directos'' en mi explicación para evitar conceptos complejos (como la asignación de punteros) para mantener las cosas muy simples. @gath hizo una pregunta básica, lo que significa que todavía está aprendiendo los principios básicos de la programación.
Ya sea como una variable de salida.
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 como una cadena.
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;