2009-02-18 9 views
13

que tienen una función de Delphi que devuelve un TStringList, pero cuando regrese un valor y tratar de usarlo me sale un error Violación de Acceso es decir¿Cómo devuelvo un objeto de una función en Delphi sin causar una infracción de acceso?

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 el TStringList y aún libre en el local de ¿función?

Respuesta

28

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 esta 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 liberar la misma. Y el código, IMO, es mucho más claro de leer y mantener.

+0

cuidado. Como las variables asignadas a la pila no se inicializan en 0, y las asignadas() solo comprueban <> nil, si no se puede crear antes de llamar, la función * no * activará la afirmación. –

+0

Buena captura, Mason. Lo corregiré en mi publicación. –

27

¿Cómo puedo devolver el TStringList y aún libre en el local de la función?

No puede. 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, pero solo copia la referencia.

Así, en lugar usted debe liberar a la lista de cadenas en el contexto de llamada después de que haya terminado de trabajar con ella o pasar la lista de cadenas como un parámetro a su función como esta

procedure FuncStringList (StringList : TStringList); 

y dejar que el código de llamada crea y libera la lista de cuerdas. Como se señala en las otras respuestas, esta es la forma preferible, ya que hace que la propiedad sea muy clara.

+1

+1, pero usted debe agregar una nota en mi humilde opinión que la segunda forma (tiene la persona que llama pasar la lista de cadenas como referencia) es el lenguaje estándar de Delphi, y que la primera es realmente sólo es adecuado para las funciones de la fábrica y la me gusta. – mghie

+0

Tiene toda la razón y edité mi respuesta como usted lo propuso. ¡Gracias! – jpfollenius

0

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?

2

Simplemente no puede liberar algo y luego espera consultarlo más adelante. Esa es la forma incorrecta. Usted tiene dos opciones básicas:

  • No llame libre, y hacer que la persona que llama responsable de la eliminación del objeto
  • tener la persona que llama pasar de un objeto de modo que es responsable tanto de crear y gratuito

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.

6

Respuesta simple: no se puede. ¿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.

+3

Exactamente mi punto de vista. Doy a funciones como esta un nombre que comienza con Crear, por lo que la persona que llama tiene la pista de que tiene que manejar el objeto devuelto de forma similar al resultado de una llamada de constructor. –

0

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.

+0

No es realmente útil, ya que la mayoría del código existente funciona con TStrings. ¿O hay alguna forma en las últimas versiones de Delphi de asignar una matriz dinámica de cadenas a un objeto TStrings? Para ordenarlo? También faltaría la propiedad Objetos. Hay suficientes razones para permanecer con TStrings. – mghie

+0

Sí, pero en su lugar debe preocuparse de ajustar manualmente la longitud de la matriz. Si implementa esto lo suficientemente ingenuo, terminará con un fragmento de código de muy mal rendimiento. Realmente prefiero usar TStringList. – jpfollenius

+0

Bueno, realmente depende de lo que esté haciendo con él; simplemente lo señalé como una posibilidad. Todo lo que veo es una elipsis. – smo

0

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 de uno de los 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 tienes es el objeto y no hay manera de recuperar el contenido en él a través de un tipo de seguro (en este caso, cuerdas) ; entonces sigo la idea de Ken White.

2

Respuesta simple (con ejemplos):

Al hacer

Resultado: = vStrList

se asigna a vStrList resultado. ¡En este momento, vStrList y Result SON LO MISMO! Entonces, en la siguiente línea de código, cuando liberas vStrList, también liberas Resultado (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 va a resolver su problema:

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

El llamador de FuncStringList tendrá que liberar "Resultado".

Se llaman 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; 

me puso esta nota aquí antes de que alguien va a empezar 'picking' en 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.

0

ambos se refieren a la misma memoria, si la libera, ambos serán liberados .......

0

O bien como 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; 

O como 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; 
Cuestiones relacionadas