| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > Delphi: Для новичков > Вызов деструктора внутри конструктора |
| Автор: PsiMagistr 1.6.2010, 18:07 | ||
| Ребят, предположим у нас есть некий класс и в нем конструктор, куда передается строковый параметр FileName (Имя файла) Возможно ли внутри реализации самого конструктора сделать проверку на существование файла и в зависимости от того, существует ли он - создавать экземпляр объекта или (если файла нет) выдать предупреждение и либо вовсе не создавать, либо разрушить объект?
Кстати в каком именно месте идет команда на выдачу объекту динамической памяти? Заранее спасибо. |
| Автор: Демо 1.6.2010, 20:59 | ||||||
Немного не так.
Есть 2 метода. Один указал kami - использовать классовую функцию:
Второй метод - поднимать исключение в конструкторе. На примере того же класса:
При возникновении в конструкторе исключения будет немедленно вызван деструктор, а объект не будет создан. Во этом случае код в деструкторе нужно писать, учитывая, что в конструкторе может возникнуть исключение. |
| Автор: kami 1.6.2010, 21:25 | ||
Ай-я... а ведь точно! Спасибо, открыл глаза |
| Автор: CodeMonkey 1.6.2010, 22:28 |
| TFileStream ведь так работает. |
| Автор: PsiMagistr 2.6.2010, 08:53 | ||
СПА-СИ-БО ВСЕМ!
1). А как именно можно словить это исключение в деструкторе? Ну хотя бы примерно? (Просто я о классах исключений слышал, но не более того. Очень поверхностно.) 2) Что есть "классовая функция"? (Даже не слыхал о таком.) Ребята, когда-то я писал ролевую игру на VB. Несмотря на то что я бросил проект, не могу сказать, что он не удался, т.к. все заложенные функции работали исправно, работа с файлами тоже была. Можно было даже бои устраивать и опыт набирать. Но там все было реализовано на уровне процедурного программирования. Ну не считая компонентов брошенных на формы. )))) Теперь мне в голову стукнуло - зашить всю начинку вовнутрь класса Сущность и его производных (Персонаж-Монстр), создать объекты и пусть все свершается в них, чтобы больше с этой рутинной работой не колебаться хотя бы внутри кода основного модуля. P.S. Потратил 2 часа написал на Дельфи "Пятнашки" (головоломка такая). Код вышел стремный, от балды, но работает. |
| Автор: Демо 2.6.2010, 09:06 | ||||
А зачем в деструкторе его отдавливать? Задача деструктора - освободить ресурсы и выполнить заключительные процедуры перед уничтожением.
Эту фразу надо понимать так: в деструкторе необходимо проверять, а действительно ли были в конструкторе выделены необходимые ресурсы. В большинстве случаев это простейшие проверки: if Assigned() и т.п. class function/procedure - специальный вид функций, которые могут выполняться без создания экземпляра класса, не имеют доступа к ссылке на экземпляр класса (Self). Пример приведён выше. |
| Автор: PsiMagistr 2.6.2010, 10:29 |
| Гм... Признаться не совсем понял. Ведь если ты сумел вызвать Free (деструктор) объекта, то значит сам объект уже был создан. Ведь он создается сразу при входе в конструктор. Раз объект создан - память под него выделена. Зачем же нужны проверки - была ли выделена память? Можно ли проверить в деструкторе, выделенна ли в конструкторе память так: If self <> nil then либо, if Assigned(self) = true then |
| Автор: Демо 2.6.2010, 11:21 | ||
Проверять нужно не сам объект, а поля и ресурсы, выделение памяти для которых и создание происходит в конструкторе. Дома буду - набросаю пример. |
| Автор: CodeMonkey 2.6.2010, 13:04 | ||
PsiMagistr, есть хороший пример и я вам в него ткнул:
Как видите, конструктор проводит создание объекта, инициализацию полей и т.п. Если при этом он сталкивается с ошибкой (например, не может открыть файл), он выбрасывает исключение. При этом Delphi автоматически вызывает деструктор. Иными словами, цепочка вызовов при этом выглядит так: CreateInstance (выделение памяти для Self) -> Create -> исключение -> Destroy -> FreeInstance (удаление памяти для Self). Сравните это со штатной ситуацией: CreateInstance (выделение памяти для Self) -> Create, далее вы работаете с объектом, вызывая в конце работы Free, Free -> Destroy -> FreeInstance (удаление памяти для Self). Поскольку в случае возникновения ошибки конструктор выполняется не полностью, то деструктор должен быть готов к удалению частично-инициализированного объекта. Что означает, что не все поля в экземпляре будут заданы. В примере выше обратите внимание, как деструктор закрывает описатель файла только, если он был открыт (if FHandle <> INVALID_HANDLE_VALUE). |
| Автор: PsiMagistr 2.6.2010, 14:38 | ||||
| CodeMonkey, благодарю. Сижу, пробую, что то странное происходит. Но обо всем по порядку. Создал проект. Добавил новый модуль. В новый модуль подключил все заголовочные функции. Подключил сам модуль к модулю формы. Описание класса (раздел interface добавленного модуля.)
Реализация методов класса (Раздел implementation модуля)
Такое впечатление, что мой деструктор очищает не всю память... |
| Автор: CodeMonkey 2.6.2010, 16:35 |
| Сыграем в игру (люблю я это дело) Буратине дали три яблока. Сколько яблок у буратины? Думаете три? Ничего подобного. Никто же не сказал, сколько яблок было до того, как ему дали три. Мораль истории: инициализируйте переменные. Способны ли вы теперь с этой подсказкой найти объяснение видимому поведению? ;) |
| Автор: PsiMagistr 2.6.2010, 18:09 |
| Благодарю, CodeMonkey. ))) Развивайте мысль. )))) О каких переменных идет речь в данном случае? Насколько я понимаю, все переменные какими мы обладаем вроде получили инициализацию, во всяком случае так считает компилятор. Внешний указатель вроде только один. MyObject. переменная-поле внутри класса fName. А теперь Ваши соображения? ) |
| Автор: CodeMonkey 2.6.2010, 18:42 | ||||||
Строка
Раскладывается на более простые действия так: - вызвать конструктор - ссылку, которую вернул конструктор, записать в переменную MyObject. Соответственно, если конструктор возбуждает исключение, то выполнение прерывается и запись чего-либо в MyObject не происходит. Откуда следует, что в MyObject остаётся то, что было в ней до вышеуказанной строчки - т.е. мусор. Очевидно, что вызов
Когда в MyObject лежит мусор, приводит к произвольному поведению. Это может быть нормальная работа, это может быть access violation - что угодно. P.S. Проверка
В деструкторе излишня - этим занимается Free. Уберите. |
| Автор: PsiMagistr 3.6.2010, 13:12 |
| Спасибо большое. Точно. Вызывали мы объект. А он не вызвался (исключение). Что в осталось в указателе? А чушь полная... Неплохо было бы в деструкторе класса прописать чтобы указатель получал хотя бы Nil. Только конечная операция деструктора опять будет inherited Destroy и оно опять мусор зашлет в указатель... Вы пишите If Assigned(self) then в деструкторе лишняя? Вот тут я не могу понять, почему? Да во Free есть эта встроенная проверка. Но именно Free нигде не вызывается. До него дело не доходит, просто. Вызывается голый деструктор. А попытка вызвать Free в деструкторе: self.free; вместо inherited Destroy; просто приведет к тому, что Free сам по себе станет вызывать деструктор, а деструктор - Free. И так без конца. |
| Автор: Демо 3.6.2010, 15:16 | ||||||
Деструктор вызывается только для созданного объекта. т.е. Self существует по-определению. Далее - предка, для которого нужно было бы вызвать свой деструктор, нет.
Вместо всего этого достаточно destructor TMyClass.Destroy; begin ShowMessage('Сейчас вызову деструктор!'); //Даю сообщение и end; |
| Автор: PsiMagistr 3.6.2010, 15:39 | ||||
Демо, благодарю.
Забавно. Я полагал, что предок всегда есть (TObject хотя бы). Правильно ли я вас понял, что невозможно вызвать деструктор для несуществующего объекта? Мне бы как-то попытаться сделать так, чтобы:
При отсутствии файла мне мало просто вызвать деструктор. Мне важно, чтобы в MyObject был nil. Но вот как его туда заслать? Ведь при исключении тут же вызывается деструктор, который разрушает все и в конечном итоге - мусор в MyObject. Или я не так понимаю? |
| Автор: Mikel 3.6.2010, 15:59 | ||
А почему бы не сделать что-н типа:
Вынести просто ту логику в инит... |
| Автор: PsiMagistr 3.6.2010, 16:33 |
| Mikel, О, это интересная идея. А что есть init? Как я понял свойство проверки инициализации? P.S. Я очень мало знаком с внутренней структурой объектов. К ООП приступил недавно. |
| Автор: Mikel 3.6.2010, 16:37 |
| Initом я просто обозвал функцию в которую предлагаю тебе вынести всю логику из Create. В Create только создавать объект. |
| Автор: PsiMagistr 3.6.2010, 16:46 |
| Mikel, благодарю от души за участие. Понимаете суть моей задачи в чем... Я загорелся идеей фикс зашить в класс всю работу с файлами. Т.е. Есть файл (или соотвествующая запись в нем) - конструктор загружает данные в поля и создает объект. Нет файла - нет объекта. А в объектном указателе в этом случае - Nil. Причем все это должно крутиться внутри класса. В этом весь смак. Обслужить все внешними проверками можно, но тогда нет особого смысла и в классе. Я хочу выжать максимум универсальности. |
| Автор: Демо 3.6.2010, 17:15 | ||||
Нет предка, для которого необходимо вызвать дополнительно деструктор. Хотя я всегда использую inherited Destroy в своём коде. Это позволяет никогда не забывать вызывать деструктор предка.
CodeMonkey уже писал тебе о том, что перед созданием объекта переменную нужно проинициализировать. т.е.
В случае возникновения исключения значение MyObject не изменится. Не путай псевдопеременную Self с обычной переменной-ссылкой на объект. |
| Автор: PsiMagistr 3.6.2010, 17:22 | ||
Демо, дружище. Именно так я и пытался писать. Более того я писал проверки, чтобы отследить:
|
| Автор: Демо 3.6.2010, 17:27 |
| А каким образом ты можешь дождаться сообщения, если на строке MyObJect:=TMyClass.Create('Проба.txt'); у тебя возникает исключение, и следующие строки не выполняются? |
| Автор: PsiMagistr 3.6.2010, 17:36 |
| А вот тут я совсем не понял чего-то. Допустим возникает исключение. И что? Оно же в конструкторе возникает. Ну конструктор, ясное дело, обваливается. Но проверка на nill не в конструкторе, она внешняя, из программы, в обработчике кнопки. По идее должен был быть переход к след шагу, неужели же обваливается вся процедура, где этот злосчастный конструктор вызван? Но тогда получается что из за исключения в конструкторе вся программа клеит ласты))) |
| Автор: bems 3.6.2010, 17:59 | ||||||
|
| Автор: PsiMagistr 3.6.2010, 18:26 |
| bems, и что вы предлагаете? Вот у меня исключение в конструкторе вызывается и весь обработчик кнопки, где я этот конструктор призвал обваливается. |
| Автор: bems 3.6.2010, 18:31 |
| PsiMagistr, да, обваливается. Ты видишь в этом что-то плохое? Если ты знаешь что делать в случае отсутствия файла, то пиши секцию except |
| Автор: Демо 3.6.2010, 19:09 | ||||||
Это ещё почему?
Ты не умеешь обрабатывать исключения?
|
| Автор: PsiMagistr 3.6.2010, 19:10 | ||
| Демо, нет. НЕ умею. Честно. Я только к дельфи приступил. С книгой сижу. Вот реализация конструктора:
Вот только как его обработать, чтоб после выполнения исключения обработчик, где будет вызываться конструктор не обваливался? В случае отсутствия файла - присвоить указателю на объект nil. Но поскольку я его и так инициализировал с nil а меняться он не должен. то в случае отсутствия можно ничего не делать. Поскольку деструктор возникнет автоматом. |
| Автор: Демо 3.6.2010, 19:52 | ||||
|
| Автор: CodeMonkey 3.6.2010, 20:32 | ||||||||||||||
Этим должен заниматься не класс. Потому что переменной может вообще не быть. Понимаете, класс работает со своими данными, с тем, что внутри. Переменная - это не его данные, она снаружи, и вообще никак не связана с классом. Но если сильно охота, то можно - в качестве примера посмотрите реализацию Application.CreateForm (вызов смотреть в DPR-файле, а реализацию - в Forms.pas). Но делать так без веских причин я бы не стал.
Потому что Self = nil в деструкторе - это глюк (почему? уже сказал Demo: при вызове из конструктора Self гарантировано не-nil, а при внешнем вызове вы вызываете Free; хотя формально вы вполне можете вызывать TForm(nil).Destroy). Такого не должно быть. А раз не должно быть, то вылететь с access violation - вполне нормальный вариант. Можете не засорять исходники лишними проверками. Только надо понимать, что "достаточно" <> "нужно так делать". Верно.
Сделать можно что угодно:
Но тогда вы сами себе буратино.
http://dl.dropbox.com/u/201788/DKArticlePDF.rar. Добавлено через 5 минут и 22 секунды Согласен с bems-ом по поводу деструктора. По той простой причине, что пустой конструктор - это деталь реализации, а не стандарт ООП языка. Вот выйдет Fulcrum - откуда вы знаете, может там в деструкторе что-то будет, чтобы сгладить косяки платформы? Вы уверены, что в FreePascal (подставьте сюда любой компилятор Паскаля, с которым вы не знакомы) деструктор тоже пустой? Но это не главное. А главное в том, что класс может быть изменён в процессе рефакторинга позже. В том числе, он может сменить предка. Вызов унаследованных конструкторов и деструкторов всегда - это простое правило, которое поможет вам избежать потенциальных проблем. |
| Автор: Демо 3.6.2010, 22:11 | ||
Да согласен я-) Смысл фразы в том, что нужно всегда знать, что делаешь. Если я знаю в данный момент, что в TObject мне ничего не нужно освобождать, то с полным сознанием и уверенностью могу не вызывать родительский деструктор, учитывая, что другоим колмпилятором я не буду пользоваться... |
| Автор: bems 4.6.2010, 00:33 | ||||
Потому что очень трудно следить за каждым конструктором на предмет озможности исключений. Правильнее считать что исключение может возникнуть в любом месте любого конструктора (собственно это не далеко от истины). Если моих слов не достаточно то вот:
А твой пример ни к селу ни к городу. Ясно, что если деструктор ничего не делает, то и проверять нечего (даже если бы в конструкторе TObject и могло возникнуть исключение) Добавлено через 12 минут и 42 секунды
что-то меня не поняли. Я говорил не о вызове унаследованного деструктора, а о том, чтобы всегда писать деструктор так, чтобы он мог разрушать недосозданный объект |
| Автор: Демо 4.6.2010, 01:11 | ||
Действительно не поняли. Я-то говорил именно об обязательности вызова родительского деструктора. |
| Автор: bems 4.6.2010, 01:28 |
Даже если это нехватка памяти или Assert? |
| Автор: Демо 4.6.2010, 09:27 |
Ну если нехватка памяти, то вряд ли можно что-то ещё сделать. А работу с Assert надо заранее проектировать-) |
| Автор: PsiMagistr 4.6.2010, 12:12 | ||||||||
| Ребята, благодарю всех. Огромное СПС! Сейчас читаю информацию предоставленную, CodeMonkey : Итак как я сделал: Реализация конструктора:
Реализация деструктора:
А в коде кнопки:
Запускать это из самой среды бесполезно - Дельфи споткнется в конструкторе. Поэтому компилирую файл. Все как будто получается. И деструктор вызывается. Но таблички:
|
| Автор: CodeMonkey 4.6.2010, 12:40 | ||||||||||
= True - излишне. Это всё равно, что спрашивать "если да равно да то" или "включен ли компьютер" (если бы компьютер был бы выключен, программа не смогла бы задать этот вопрос). Надо:
Это уведомление отладчика сделано исключительно для вашего удобства, чтобы вы могли исследовать ситуацию, приводящую к ошибке, прямо на месте. Если вы не хотите этого делать - просто жмите Continue (в новых Delphi) или Run/Run (в старых). Вы также можете отключить эти уведомления в настройках среды, но я бы не стал этого делать - это исключительно полезный механизм. Вы же сами её заблокировали блоком except. Далее, проверять в except MyObject на nil - излишне, т.к. если вы попали в блок except, то только потому, что создание объекта обвалилось. Это всё равно, что проверять Self <> nil в деструкторе. Поэтому:
А вот и табличка:
Или:
В общем, миллион способов. А выбор зависит от того, зачем это надо, и что вы будете делать дальше. И я бы, на вашем месте, рассказал бы побольше про это. |
| Автор: PsiMagistr 4.6.2010, 12:57 |
| Я попробую в двух словах. Решил писать ролевую игру. Когда то давно писал на VB первый экземпляр. Получилось. Правда реализовывать ООП я не стал, обошелся процедурным программированием. Основная текущая задача - зашить в класс работу с файлами. Вот например файл персонажа. Изначально там служебная информация, недоступная игроку. (параметры умолчания). Например: Жизнь-100 Интеллект-50 Ловкость=30 И .т.д. Что это значит. Когда мы создаем нового персонажа, конструктор объекта "Персонаж" читает служебную запись файла, считывает переменные умолчания и на основании этого строит вторую запись - это уже реальный персонаж, доступ к которому имеет игрок. В классе я пытаюсь охватить все возможные нюансы работы с файлами. Например обращение из основной программы: MyObject.Lovkost:=30; к свойству "Ловкость", будет означать не только присвоение, но и запись соответствующей информации в файл. |
| Автор: CodeMonkey 4.6.2010, 13:57 | ||
| Так, а теперь, внимание, вопрос (С) Положим вы создаёте персонажа из файла TMyObject.Create('SavedGame01.dat'); Положим файл не найден, заблокирован антивирусом или тупо повреждён - вы выбрасываете исключение, отлично. Но. Дальше-то вы что делаете? А дальше вы обрабатываете эту ошибку, показывая мессагу, производя откат или что там ещё придумаете. Но при этом вы не обращаетесь к MyObject. Почему? Потому что он был не создан. Зачем вам обращаться к несуществующему объекту? Я имею ввиду, что вы просто не доходите до этого кода, например:
Если создание объекта проваливается, то до использования объекта ниже вы не доходите. Именно поэтому я сказал, что ваши попытки сделать MyObJect := nil выглядят очень подозрительно. Как, в целом, узнают, что MyObJect <> nil? http://www.transl-gunsmoker.ru/2008/12/blog-post_7745.html. |
| Автор: PsiMagistr 4.6.2010, 14:38 |
| CodeMonkey, благодарен безмерно. попробую Вам объяснить в чем штука со всеми нюансами. Итак, у нас есть файл записей (record). Первая запись данного файла - это служебная информация прототипа. Доступ игрока к ней закрыт. Остальные записи в файле это параметры реальных персонажей. В классе будут ДВА конструктора CreateOpen и CreateNew. Если игрок будет открывать уже созданного персонажа то вызываем конструктор CreateOpen, куда передадим лог-пасс. CreateOpen будет проверять существование файла. Если нет, генерируем сообщение "Не хватает служебных ресурсов". Обваливаем конструктор исключением и закрывем программу. Если файл есть, обходим его ночным дозором , поочередно забираем в переменные-поля параметры каждой записи и сравниваем их лог-пассы с лог-пассом введенным пользователем. Если совпадает то: Грузим конструктором туда все данные из найденной записи. И создаем, наконец, объект. А если не совпадает: Генерируем сообщение "Неверный лог-пасс", обрушиваем конструктор и не создаем объект. Введет пользователь новый лог-пасс, нажмет на кнопочку и... Будет новая попытка создания объекта. Второй конструктор CreatNew занимается другим. Он Проверяет, существует ли файл. Если да, то берет служебную первую запись. Грузит значения в переменные. Берет лог-пасс введенный пользователем снова грузит в переменные (подменяя там формальные значения-умолчания взятые из служебной записи). и наконец создает вторую запись. Вторая запись - действительный персонаж. К нему впоследствии обращается игрок. |
| Автор: CodeMonkey 4.6.2010, 14:47 | ||
| Ээээ.... ну? Что-то я не понял, что вы хотели сказать. Зачем тогда вам в этом сценарии обнуление переменной MyObject при неудаче? Если в обоих случаях при исключении вы просто игнорируете переменную и не обращаетесь к ней? P.S. Конструктор не обязательно должен начинаться с Create. Удобно создать два конструктора так:
Ибо вызовы TMyObject.Create(...) и TMyObject.Open(...) смотрятся как-то "читабельнее", нежели TMyObject.CreateNew(...) и TMyObject.CreateOpen(...). |
| Автор: PsiMagistr 4.6.2010, 14:58 |
| CodeMonkey, свершенно верно. В реальности в случае обрушения в конструкторе в указателе может остаться даже "мусор умолчания" (хотя по моему по умолчанию указатель равен - nil). Объекта то все равно нет и делать нам нечего. Но мусор для меня нежелателен, хочу чтоб все под контролем было . |
| Автор: PsiMagistr 4.6.2010, 16:35 | ||
Вот еще какая тонкая штука, нюансик.
ДАННЫЙ ВОПРОС СНИМАЕТСЯ. Я голова садовая, совсем забыл, что Free контролирует указатель и только в случае указатель <> Nil вызывает деструктор. |
| Автор: CodeMonkey 4.6.2010, 18:37 | ||||||||||
Так, давайте ещё раз. Есть два сценария использования любого объекта. Первый - локально:
Как видите, ничего страшного в том, что у вас в MyObject видит мусор нет. Потому что если возникнет исключение, вы вообще выйдете из процедуры, и сама переменная даже исчезнет. Сценарий 2: глобально Здесь MyObject - глобальная переменная или поле объекта (например, формы)
Как видите, и здесь не надо никуда nil присваивать. Хотя вы обращаетесь к переменной после исключения, но там уже не мусор, а nil, потому что поля объекта и глобальные переменные не содержат мусор, а содержат нули. P.S. Вместо
Лучше делать
|
| Автор: PsiMagistr 6.6.2010, 12:46 |
| Благодарю сердечно, CodeMonkey. |
| Автор: PsiMagistr 8.6.2010, 12:42 | ||||
| Скоро буду готов к описанию классов. Возник следующий нюанс: Все переменные класса (поля), должны сохраняться в файле записей (record). Следовательно объявить их просто полями как:
Представляется невозможным, так как, все поля должны сохраняться в одной записи (файла). Можно решить дело так:
Но это представляется мне тоже довольно неудобным. Тем более что класса в программе три (базовый класс Сущность и его наследники Персонаж и Монстр). И все они отличаются количеством полей. При работе с файлами есть ли разница используем ли FileStream или стандартные команды: (Write Reset)? |
| Автор: CodeMonkey 8.6.2010, 13:46 |
| Вообще-то, для классов нет нужды использовать записи. Вы можете сделать поля, которые хотите сохранять, published и сохранять в поток (TStream, в частности - TFileStream) обычными средствами. Класс при этом должен наследоваться от TPersistent. |
| Автор: PsiMagistr 8.6.2010, 14:24 |
| Да? Это интересно. А поподробней, если можно? Как сохранить через объект класса TFileStream запись, представляю. А вот поля класса... Наткнулся на умную статью: http://forum.vingrad.ru/topic-94245/view-all.html Но к сожалению понял мало что... |
| Автор: Rennigth 8.6.2010, 14:42 |
А что конкретно не понятно? Вроде там все с примерами... |
| Автор: PsiMagistr 8.6.2010, 14:59 |
| Пример хорош, но... Слишком много тонкостей. Цитата: "модуль для сохранения и чтения объектов". (Почему именно объектов, а не данных полей класса? Что значит сохранить объект? Голова кругом от научной терминологии) Что есть: TWriter, TReader. Как они работают? И подробней о теории потоков (TStream). Кроме того у меня должен быть именно файл записей. И каждая запись в файле - индивидуальный набор параметров. |
| Автор: PsiMagistr 8.6.2010, 17:39 |
| Смотрю статью. Ковыряюсь в коде примера. Что интересно: там процедуры записи-чтения объекта (полей) являются внешними. А мне бы их в класс встроить. Попробую завтра эксперимент сделать. |