Delphi Seattle: я получаю операцию Invalid Pointer при освобождении созданного объекта

Я использую Делфи Сиэтл.

Моя проблема возникает, когда я пытаюсь освободить объект, который я создал.

Я искал на этом сайте (а также на других сайтах) уже опубликованные ответы на этот вопрос, но все они немного отличаются. Согласно этим обсуждениям, мой код должен работать, но явно что-то не так.

Итак, мне нужна помощь...

Поток выполнения:

а) в форме fmLoanRequest я создаю объект на основе класса TStorageLoan (подкласс TLoan). Конструктор загружает всевозможные значения в некоторые атрибуты объекта (теперь показанные здесь).

б) Позже я передаю адрес объекта в другую форму (fmLoan) в соответствующую общедоступную переменную. fmLoan — это форма, в которой происходят все операции пользователя с содержимым Loan. Обратите внимание, что fmLoanRequest остается как есть, пока мы находимся в fmLoan. Мы вернемся к fmLoanrequest, когда fmLoan закроется.

в) Отображается форма fmLoan (и показывает данные в объекте - все работает хорошо).

г) При закрытии fmLoan вызывается процедура Освобождения объекта Loan - если он присвоен (см. строку 10 второго фрагмента кода). Кажется, работает нормально (без ошибок).

e) Ошибка «Недопустимая операция указателя» возникает при выполнении кода в строке 14 ниже: ( if Assigned(oLoan) then oLoan.Free; ).

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

Частичный код из формы fmLoanRequest (я добавил несколько номеров строк для справки)

1  // In form fmLoanRequest 
2  // Create new Loan Object (from a Loan sub-class as it happens)
3    // Create the object here; Object address will be passed to fmLoan later for handling.
4    oLoan := TStorageLoan.Create(iNewLoanID);
5  ...
6  ...
7     fmLoan.oLoan := oLoan; // pass the address to the other form
8     fmLoan.show;
9     // User would click the 'btnClose' at this point. See event code below.
10  ...
11  ...
12    procedure TfmLoanRequests.btnCloseClick(Sender: TObject);
13    begin 
14      if Assigned(oLoan) then oLoan.Free; // <--- ERROR HERE 
15      fmLoanRequests.Close;
16  end;

Частичный код из формы fmLoan (я добавил несколько номеров строк для справки)

1  //Form fmLoan
2  ...
3    public
4      oLoan : TLoan;
5  ... 
6  // In form fmLoan, I call the following upon closing the Form
7  //                 in the OnClick event of the 'btnClose' button. 
8  Procedure TfmLoan.Clear_Loan_Object;
9  begin
10    if Assigned(oLoan) then oLoan.Free; // <-- THIS WORKS FINE
11  end;

Должен ли я попробовать другой подход?

Должен ли я просто удалить эту строку (строка 14 - первый фрагмент кода) и надеяться на лучшее. Это совсем не моя философия правильного кодирования!

Я иду к этому неправильно?

Примечание. Очевидно, я не использую указатели.

Любая помощь будет оценена по достоинству!


person Jack N.    schedule 02.03.2016    source источник
comment
Вы всегда можете использовать FreeAndNil(oLoan) во всех местах, где вы используете oLoan.free. Таким образом, ваши тесты для Assigned(oLoan) вернут true, когда вы этого ожидаете.   -  person nolaspeaker    schedule 15.03.2016


Ответы (2)


Понятно, что вы дважды освобождаете объект Loan, поэтому и получаете ошибку. Вам нужно освободить его только один раз. fmLoanRequests создает объект, но вы говорите, что он может быть закрыт до закрытия fmLoan, поэтому fmLoan должен стать владельцем объекта и освободить его, когда fmLoan закроется. Не освобождайте объект вообще, когда fmLoanRequest закрыт.

Альтернативой является определение интерфейса ILoan, который реализуют TLoan и потомки, а затем передача ILoan вместо TLoan напрямую. Интерфейсы подсчитываются по ссылкам, поэтому объект Loan будет освобожден автоматически и только один раз, после того как fmLoanRequests и fmLoan освободит свои ссылки на него.

person Remy Lebeau    schedule 02.03.2016
comment
Я неправильно понял, что Assigned() работает как сборщик мусора в Java (проверяя, есть ли у Referenced Object другие ссылки. Это явно неверно — Assigned() проверяет, содержит ли ссылочная переменная какой-либо адрес (действительный или нет). - person Jack N.; 04.03.2016
comment
В Delphi нет сборки мусора. Assigned() просто проверяет, является ли указанный указатель нулевым или нет, и ничего больше. Однако на мобильных платформах Delphi поддерживает ARC (автоматический подсчет ссылок) для объектов, и TObject имеет свойства RefCount и Disposed в системах ARC. - person Remy Lebeau; 04.03.2016

Я добавил эту строку, чтобы убедиться, что объект будет освобожден, если fmLoan по какой-то причине не справится с ним. Я понимаю, что к этому моменту объект был освобожден, но разве if Assigned() не должно предотвращать ненужное освобождение объекта?

Это ключевое недоразумение. Рассмотрим следующую программу:

{$APPTYPE CONSOLE}

var
  obj: TObject = nil;

begin
  Writeln(Assigned(obj));

  obj := TObject.Create;
  Writeln(Assigned(obj));

  obj.Free;
  Writeln(Assigned(obj));

  Readln;
end.

Это выводит следующее:

FALSE
TRUE
TRUE

Обратите внимание, что последняя строка вывода — TRUE. Другими словами, когда вы уничтожаете объект, вызывая его метод Free, ссылочной переменной не присваивается значение nil.

Ваша ошибка в том, что вы считаете, что Assigned проверяет, был ли объект уничтожен. Это не так. Он просто проверяет, является ли ссылочная переменная nil или нет. Давайте еще раз посмотрим на код более подробно.

obj := TObject.Create;

Здесь мы создаем новый объект, размещенный в куче, с вызовом TObject.Create. Мы также присваиваем obj адрес или ссылку на этот объект. После выполнения этой строки obj становится ссылочной переменной, содержащей адрес допустимого объекта.

obj.Free;

Это уничтожает объект, на который ссылается obj. Запускается деструктор, после чего память уничтожается. После выполнения этой строки объект был уничтожен, но obj по-прежнему относится к этому уничтоженному и теперь недействительному фрагменту памяти. Вот почему Assigned(obj) дает true.

Примечание. Очевидно, я не использую указатели.

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

Я написал подробный ответ на всю эту тему для другого вопроса. Я предлагаю вам прочитать этот ответ: https://stackoverflow.com/a/8550628/505088.

Один из моментов, который вы уберете, это такой код, как

if Assigned(oLoan) then 
  oLoan.Free;

бессмысленно. Метод Free также проверяет, является ли ссылка на объект nil. Эта строка кода фактически расширена до:

if Assigned(oLoan) then 
  if Assigned(oLoan) then 
    oLoan.Destroy;

Итак, вместо

if Assigned(oLoan) then 
  oLoan.Free;

вы должны просто написать

oLoan.Free;

Теперь вернемся к нарушению доступа. Я думаю, что теперь должно быть очевидно, что вы пытаетесь уничтожить объект, который уже был уничтожен. Вы не должны этого делать. Вам нужно будет пересмотреть свой жизненный цикл. Рассуждения типа "если бы fmLoan по какой-то причине не разобрался с этим" действительно недостаточно хороши. Вы должны быть на 100% уверены в управлении жизненным циклом. Вы должны убедиться, что ваши объекты уничтожаются ровно один раз. Не имея возможности видеть весь ваш код, я не хочу давать конкретных рекомендаций.

Один шаблон, который иногда бывает полезен, заключается в установке ссылки на объект на nil при уничтожении объекта. Если объект может быть уничтожен в нескольких местах, этот метод можно использовать, чтобы убедиться, что вы не пытаетесь уничтожить его дважды. Вы даже можете использовать вспомогательную функцию FreeAndNil. Однако стоит подчеркнуть, что если вы не уверены, уничтожили ли вы уже объект, то это обычно свидетельствует о плохом дизайне. Если вы обнаружите, что хотите добавить вызовы Free, чтобы действовать «на всякий случай», то вы почти наверняка делаете что-то серьезно неправильное.

person David Heffernan    schedule 03.03.2016
comment
Спасибо - я понял, что тест Assigned() эквивалентен тесту сборщика мусора Java, чтобы увидеть, ссылаются ли какие-либо другие местоположения на этот объект (если да, оставьте его в покое; если нет, его можно удалить). Моя ошибка! Я ценю все объяснения и примеры - мой код БУДЕТ лучше для них! - person Jack N.; 04.03.2016