Как вернуть объект из функции в Delphi без нарушения доступа?

У меня есть функция delphi, которая возвращает TStringList, но когда я возвращаю значение и пытаюсь его использовать, я получаю ошибку нарушения прав доступа i.e

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;

Как я могу вернуть TStringList и освободить его в локальной функции?

Ответ 1

Как сказал Смашер, вы не можете освободить его; код, вызывающий функцию, возвращающую объект, несет ответственность за ее уничтожение.

Это плохой дизайн кода, кстати, поскольку он путает то, кто распределяет и освобождает. Гораздо лучший способ сделать это - заставить вызывающего создать объект и передать его функции. Таким образом, код, который его создает, также освобождает его. Что-то вроде этого:

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;

Теперь нет путаницы в том, кто что делает - тот же код, который создает объект, несет ответственность за его освобождение. И код, IMO, гораздо яснее читать и поддерживать.

Ответ 2

Как я могу вернуть TStringList и освободить его в локальной функции?

Вы не можете. Если вы освободите его в локальной функции, вы не сможете использовать возвращаемое значение. Результат и vStrList указывают на тот же объект TStringList в памяти. TStringList - это класс и

Result := vStrList

поэтому не копирует список строк, а только копирует ссылку.

Итак, вместо этого вы должны освободить список строк в вызывающем контексте после того, как вы закончите работать с ним, или передайте список строк в качестве параметра вашей функции, например,

procedure FuncStringList (StringList : TStringList);

и позвольте вызывающему коду создавать и освобождать список строк. Как указывалось в других ответах, это предпочтительный путь, поскольку он делает право собственности очень ясным.

Ответ 3

Простой ответ: вы не можете. Почему вы пытаетесь? Это потому, что вы узнали, что вам нужно освободить каждый созданный объект в той же самой функции, в которой они созданы? Это обычно правильно, но не всегда, и это одно из исключений из правила. Лучший способ сказать, что каждый объект должен быть освобожден его владельцем.

Если у вас есть функция, которая генерирует объект, как этот, но затем передает его другой функции, она не получает права собственности на объект. Удалите вызов, чтобы освободить его и задокументировать, так что вы (и кто-либо другой, кто использует эту функцию) поймут, что он создает новый объект, который должен вызвать код, который его вызывает.

Ответ 4

Простой ответ (с примерами):

Когда вы делаете

Результат: = vStrList

Вы присваиваете vStrList Результату. В этот момент vStrList и результат - это то же самое! Итак, в следующей строке кода, когда вы освобождаете vStrList, вы освобождаете и Result (ну, это не ТЕХНИЧЕСКИ точно, но я использовал его для упрощения объяснения). Вот почему вы получаете AV, когда вы пытаетесь использовать результат функции. Результат был уничтожен, когда вы освободили vStrList.

Итак, это решит вашу проблему:

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

Вызывающий FuncStringList ДОЛЖЕН освободить "Результат".

Вы называете это так:

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

,

Другой пример:

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; 

Я поместил эту заметку здесь, прежде чем кто-нибудь начнет "выбирать" мое объяснение. Я использовал несколько "ярлыков" в своем объяснении, чтобы избежать сложных понятий (таких как назначение указателей), чтобы все было очень просто. @gath задал основной вопрос, который означает, что он все еще изучает основы программирования.

Ответ 5

Вы просто не можете что-то освободить, а затем рассчитываете ссылаться на него позже. Это неправильный путь. У вас есть два основных варианта:

  • Не звоните бесплатно, а вызывающий абонент несет ответственность за удаление объекта
  • Попросите абонента передать объект, чтобы он отвечал за создание и создание

Первый вариант кажется более простым, уменьшает интерфейс для функции и т.д. Второй вариант делает использование менее подверженным ошибкам, поскольку он интуитивно понятен вызывающему, ответственному за управление объектом.

Ответ 6

Не освобождайте объект до того, как вы сделаете вызов методов на нем. Вы в настоящее время вызываете метод Count на уничтоженном объекте, следовательно, ошибка.

Почему бы вам не создать список строк в вызывающей функции и передать его ссылку на метод, который заполняет его? Или сделать список строк членом класса и освободить его, когда вы освободите класс, который его владеет?

Ответ 7

Другая возможность - использовать динамический массив вместо TStringList. Поскольку массивы подсчитаны, вам никогда не придется беспокоиться о том, чтобы освободить ее.

Ответ 8

Политика, с которой я сталкиваюсь, заключается в том, чтобы передать содержимое списка строк через свойство text и просто передать строку, возвращаемую функции. Таким образом, нет необходимости обсуждать, кто выпускает кого. Конечно, вы должны сделать немного больше кодирования, но это безопаснее. Примером является адаптация Кена Уайта:

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;

Исключением является то, что все, что у вас есть, является объектом, и нет способа получить контент на нем с помощью безопасного типа (в данном случае строки); затем я следую за идеей Кена Уайта.

Ответ 9

оба записываются в ту же память, если вы освободите ее, оба освободятся...

Ответ 10

Либо как переменная 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;

Или как строка.

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;