Из Википедии, бесплатной энциклопедии
Перейти к навигации Перейти к поиску

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

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

Обзор [ править ]

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

Термины обычно идут в парах антонимов, один для концепции создания, другой для соответствующей концепции уничтожения, такой как инициализация / завершение или конструктор / деструктор. Пара создание / уничтожение также известна , среди прочего, как инициирование / завершение . Термины « выделение» и « освобождение» или « освобождение» также используются по аналогии с управлением памятью, хотя создание и уничтожение объектов могут включать в себя значительно больше, чем просто выделение и освобождение памяти, а выделение / освобождение более правильно считать этапами создания и уничтожения, соответственно.

Детерминизм [ править ]

Основное различие заключается в том, является ли время жизни объекта детерминированным или недетерминированным. Это зависит от языка и внутри языка зависит от распределения памяти объекта; время жизни объекта может отличаться от времени жизни переменной.

Объекты со статическим распределением памяти , особенно объекты, хранящиеся в статических переменных , и модули классов (если классы или модули сами являются объектами и выделяются статически), имеют тонкий недетерминизм во многих языках: в то время как их время жизни, похоже, совпадает со временем выполнения В программе порядок создания и уничтожения - какой статический объект создается первым, какой второй и т. д. - обычно недетерминирован. [а]

Для объектов с автоматическим распределением памяти или динамическим распределением памяти , создание объекта , как правило происходит детерминировано, либо в явном виде , когда объект создан явно (например, с помощью newв C ++ или Java), или неявно в начале переменной жизни, в частности , когда объем А.Н. вводится автоматическая переменная , например, при объявлении. [b] Уничтожение объектов, однако, различается - в некоторых языках, особенно в C ++, автоматические и динамические объекты уничтожаются в детерминированное время, например, при выходе из области видимости, явном уничтожении (посредством ручного управления памятью ) или счетчика ссылок.достижение нуля; в то время как в других языках, таких как C #, Java и Python, эти объекты уничтожаются в недетерминированное время, в зависимости от сборщика мусора, и воскрешение объекта может произойти во время уничтожения, увеличивая время жизни.

В языках со сборкой мусора объекты обычно выделяются динамически (в куче), даже если они изначально привязаны к автоматической переменной, в отличие от автоматических переменных с примитивными значениями, которые обычно автоматически выделяются (в стеке или в регистре). Это позволяет возвращать объект из функции («escape») без уничтожения. Однако в некоторых случаях возможна оптимизация компилятора , а именно выполнение анализа выхода и доказательство того, что выход невозможен, и, таким образом, объект может быть размещен в стеке; это важно в Java. В этом случае уничтожение объекта произойдет незамедлительно - возможно, даже во время жизни переменной (до конца ее области видимости), если она недоступна.

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

Шаги [ править ]

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

Аналогично уничтожение объекта можно разбить на две операции в обратном порядке: завершение и освобождение памяти . У них нет аналогичных концепций уровня языка для переменных: время жизни переменной заканчивается неявно (для автоматических переменных - при раскручивании стека; для статических переменных - при завершении программы), и в это время (или позже, в зависимости от реализации) память освобождается, но доработки в целом не делается. Однако, когда время жизни объекта привязано к времени жизни переменной, конец времени жизни переменной вызывает финализацию объекта; это стандартная парадигма C ++.

Вместе они дают четыре этапа на уровне реализации:

выделение, инициализация, завершение, освобождение

Эти шаги могут выполняться автоматически средой выполнения языка, интерпретатором или виртуальной машиной или могут быть указаны вручную программистом в подпрограмме , конкретно с помощью методов - частота этого значительно варьируется между шагами и языками. Инициализация очень часто задается программистом в языках , основанных на классах , тогда как в строгих языках , основанных на прототипах, инициализация выполняется автоматически путем копирования. Финализация также очень распространена в языках с детерминированным разрушением, особенно в C ++, но гораздо реже в языках со сборкой мусора. Распределение указывается реже, а освобождение, как правило, не может быть указано.

Статус во время создания и уничтожения [ править ]

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

В случае сбоя создания или уничтожения сообщение об ошибке (часто путем создания исключения) может быть затруднено: объект или связанные объекты могут находиться в несогласованном состоянии, а в случае разрушения, которое обычно происходит неявно и, следовательно, в неопределенной среде - обработка ошибок может быть затруднена. Противоположная проблема - входящие исключения, а не исходящие исключения - заключается в том, должно ли создание или уничтожение вести себя по-разному, если они происходят во время обработки исключений, когда может быть желательно другое поведение.

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

Программирование на основе классов [ править ]

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

Отношения между этими методами могут быть сложными, и язык может иметь как конструкторы, так и инициализаторы (например, Python), или как деструкторы, так и финализаторы (например, C ++ / CLI ), или термины «деструктор» и «финализатор» могут относиться к языку- конструкция уровня по сравнению с реализацией (как в C # по сравнению с CLI).

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

Обычно конструктор - это метод, явно вызываемый пользовательским кодом для создания объекта, а «деструктор» - это подпрограмма, вызываемая (обычно неявно, но иногда явно) при уничтожении объекта в языках с детерминированным временем жизни объекта - архетип - C ++. - а «финализатор» - это подпрограмма, неявно вызываемая сборщиком мусора при уничтожении объекта в языках с недетерминированным временем жизни объекта - архетип - Java.

Шаги во время завершения значительно различаются в зависимости от управления памятью: при ручном управлении памятью (как в C ++ или ручном подсчете ссылок), ссылки должны быть явно уничтожены программистом (ссылки очищены, счетчики ссылок уменьшены); при автоматическом подсчете ссылок это также происходит во время финализации, но автоматически (как в Python, когда это происходит после вызова финализаторов, указанных программистом); и при отслеживании сборки мусора в этом нет необходимости. Таким образом, при автоматическом подсчете ссылок финализаторы, указанные программистом, часто бывают короткими или отсутствуют, но все же может быть проделана значительная работа, в то время как при трассировке сборщиков мусора финализация часто не требуется.

Управление ресурсами [ править ]

В языках, где объекты имеют детерминированное время жизни, время жизни объекта может использоваться для совместного управления ресурсами : это называется идиомой получения ресурсов - это инициализация (RAII): ресурсы приобретаются во время инициализации и высвобождаются во время завершения. В языках, где объекты имеют недетерминированное время жизни, особенно из-за сборки мусора, управление памятью обычно осуществляется отдельно от управления другими ресурсами.

Создание объекта [ править ]

В типичном случае процесс выглядит следующим образом:

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

Эти задачи могут быть выполнены сразу, но иногда остаются незавершенными, а порядок выполнения задач может варьироваться и может вызывать несколько странное поведение. Например, при множественном наследовании сложно ответить на вопрос, какой инициализирующий код следует вызвать первым. Однако конструкторы суперкласса должны вызываться перед конструкторами подкласса.

Создание каждого объекта как элемента массива - сложная проблема. [ требуется дополнительное объяснение ] Некоторые языки (например, C ++) оставляют это на усмотрение программистов.

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

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

Методы создания [ править ]

Способ создания объектов зависит от языка. В некоторых языках, основанных на классах, специальный метод, известный как конструктор , отвечает за проверку состояния объекта. Как и обычные методы, конструкторы могут быть перегружены , чтобы можно было создать объект с различными указанными атрибутами. Кроме того, конструктор - единственное место, где можно установить состояние неизменяемых объектов [ Требуются неправильные пояснения ] . Конструктор копии является конструктором , который принимает (единственный) параметр существующего объекта того же типа , как класс конструктора, и возвращает копию объекта , отправляемый в качестве параметра.

Другие языки программирования, такие как Objective-C , имеют методы класса, которые могут включать в себя методы типа конструктора, но не ограничиваются простым созданием экземпляров объектов.

C ++ и Java подвергались критике [ кем? ] для того, чтобы не предоставлять именованные конструкторы - конструктор всегда должен иметь то же имя, что и класс. Это может быть проблематично, если программист хочет предоставить два конструктора с одинаковыми типами аргументов, например, для создания точечного объекта либо из декартовых координат, либо из полярных координат , оба из которых будут представлены двумя числами с плавающей запятой. Objective-C может обойти эту проблему, поскольку программист может создать класс Point с методами инициализации, например, + newPointWithX: andY: и + newPointWithR: andTheta:. В C ++ нечто подобное можно сделать с помощью статических функций-членов. [1]

Конструктор также может относиться к функции, которая используется для создания значения помеченного объединения , особенно в функциональных языках.

Уничтожение объекта [ править ]

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

В языках на основе классов с детерминированным временем жизни объекта, особенно в C ++, деструктор - это метод, вызываемый при удалении экземпляра класса перед освобождением памяти. В C ++ деструкторы отличаются от конструкторов по-разному: они не могут быть перегружены, не должны иметь аргументов, не должны поддерживать инварианты классов и могут вызывать завершение программы, если они вызывают исключения.

В языках сборки мусора объекты могут быть уничтожены, когда они больше не доступны для работающего кода. В языках с GC на основе классов аналогом деструкторов являются финализаторы , которые вызываются перед сборкой объекта сборщиком мусора. Они отличаются тем, что работают в непредсказуемое время и в непредсказуемом порядке, поскольку сборка мусора непредсказуема, значительно реже используется и менее сложна, чем деструкторы C ++. Примеры таких языков включают Java , Python и Ruby .

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

Примеры [ править ]

C ++ [ править ]

class  Foo  {  public :  // Это объявления прототипов конструкторов.  Foo ( int  x );  Foo ( int  x ,  int  y );  // Перегруженный конструктор.  Foo ( const  Foo  & старый );  // Копируем конструктор.  ~ Foo ();  // Деструктор. };Foo :: Foo ( int  x )  {  // Это реализация  // конструктора с одним аргументом. }Foo :: Foo ( int  x ,  int  y )  {  // Это реализация  // конструктора с двумя аргументами. }Foo :: Foo ( const  Foo  & old )  {  // Это реализация  // конструктора копирования. }Foo :: ~ Foo ()  {  // Это реализация деструктора. }int  main ()  {  Фу-  фу ( 14 );  // Вызов первого конструктора.  Foo  foo2 ( 12 ,  16 );  // Вызов перегруженного конструктора.  Foo  foo3 ( foo );  // Вызов конструктора копирования. // Деструкторы вызываются в обратном порядке  // здесь автоматически. }

Java [ править ]

class  Foo {  public  Foo ( int  x )  {  // Это реализация  // конструктора с одним аргументом  } public  Foo ( int  x ,  int  y )  {  // Это реализация  // конструктора с двумя аргументами  } public  Foo ( Foo  old )  {  // Это реализация  // конструктора копирования  } public  static  void  main ( String []  args )  {  Foo  foo  =  new  Foo ( 14 );  // вызов первого конструктора  Foo  foo2  =  new  Foo ( 12 ,  16 );  // вызов перегруженного конструктора  Foo  foo3  =  new  Foo ( foo );  // вызов конструктора копирования  // сборка мусора происходит незаметно, а объекты уничтожаются  } }

C # [ править ]

namespace  ObjectLifeTime  { class  Foo {  public  Foo ()  {  // Это реализация  // конструктора по умолчанию.  } public  Foo ( int  x )  {  // Это реализация  // конструктора с одним аргументом.  }  ~ Foo ()  {  // Это реализация  // деструктора.  }  public  Foo ( int  x ,  int  y )  {  // Это реализация  // конструктора с двумя аргументами.  }  public  Foo ( Foo  old )  {  // Это реализация  // конструктора копирования.  }  public  static  void  Main ( string []  args )  {  Foo  defaultfoo  =  new  Foo ();  // Вызов конструктора по умолчанию  Foo  foo  =  new  Foo ( 14 );  // Вызов первого конструктора  Foo  foo2  =  new  Foo ( 12 ,  16 );  // Вызов перегруженного конструктора  Foo  foo3  =  new  Foo ( foo );  // Вызов конструктора копирования  } }}

Objective-C [ править ]

#import <objc / Object.h>@interface  Point  : Object {  двойной  x ;  двойной  y ; }// Это методы класса; мы объявили два конструктора +  ( Point  * )  newWithX:  ( double )  иY :  ( double ); +  ( Точка  * )  newWithR:  ( двойной )  andTheta :  ( двойной );// Методы экземпляра -  ( Point  * )  setFirstCoord:  ( double ); -  ( Точка  * )  setSecondCoord:  ( двойной );/ * Поскольку Point является подклассом универсального класса Object *, мы уже получили общие методы выделения и инициализации *, + alloc и -init. Для наших конкретных конструкторов * мы можем сделать их из этих методов, которые мы * унаследовали. * / @end @  точка реализации-  ( Точка  * )  setFirstCoord:  ( double )  new_val {  x  =  new_val ; }-  ( Точка  * )  setSecondCoord:  ( double )  new_val {  y  =  new_val ; }+  ( Point  * )  newWithX:  ( double )  x_val  andY:  ( double )  y_val {  // Лаконично написанный метод класса для автоматического выделения и  // выполнения определенной инициализации.  возвращение  [[[ Точка  Alloc ]  setFirstCoord : x_val ]  setSecondCoord : y_val ];  }+  ( Point  * )  newWithR:  ( double )  r_val  andTheta:  ( double )  theta_val {  // Вместо того, чтобы выполнять то же самое, что и выше, мы можем скрытно  // использовать тот же результат, что и предыдущий метод  return  [ Point  newWithX : r_val  andY : theta_val ]; }@конецint main ( void ) {  // Строит две точки p и q.  Point  * p  =  [ Point  newWithX : 4.0  andY : 5.0 ];  Point  * q  =  [ Point  newWithR : 1.0  andTheta : 2.28 ]; //... текст программы ....  // Мы закончили с p, скажем, освободим его.  // Если p выделяет больше памяти для себя, может потребоваться  // переопределить метод Object free, чтобы  //  рекурсивно освободить память p. Но это не так, поэтому мы можем просто [ p  free ]; //... больше текста ... [ q  бесплатно ]; возврат  0 ; }

Object Pascal [ править ]

Связанные языки: «Delphi», «Free Pascal», «Mac Pascal».

 пример программы ;тип DimensionEnum  =  (  deUnassigned ,  de2D ,  de3D ,  de4D  ) ; PointClass  =  частный класс  Dimension : DimensionEnum ;   общедоступный  X :  целое число ;  Y :  целое число ;  Z :  целое число ;  T :  целое число ; общедоступный  (* прототип конструкторов *) конструктор  Create () ;  конструктор  Create ( AX ,  AY :  Integer ) ;  конструктор  Create ( AX ,  AY ,  AZ :  Integer ) ;  конструктор  Create ( AX ,  AY ,  AZ ,  ATime :  Integer ) ;  конструктор  CreateCopy ( APoint :  PointClass ) ; (* прототип деструкторов *) деструктор  Destroy ;  конец ;конструктор  PointClass . Создать () ; begin  // реализация универсального конструктора без аргументов  Self . Размерность  : =  deUnassigned ; конец ;конструктор  PointClass . Создать ( AX ,  AY :  целое число ) ; begin  // реализация конструктора с двумя аргументами  Self . X  : =  AX ;  Y  : =  AY ; Самостоятельная . Размерность  : =  de2D ; конец ;конструктор  PointClass . Создать ( AX ,  AY ,  AZ :  целое число ) ; begin  // реализация конструктора с 3 аргументами  Self . X  : =  AX ;  Y  : =  AY ;  Самостоятельная . X  : =  AZ ; Самостоятельная . Размерность  : =  de3D ; конец ;конструктор  PointClass . Создать ( AX ,  AY ,  AZ ,  ATime :  Integer ) ; begin  // реализация конструктора с 4 аргументами  Self . X  : =  AX ;  Y  : =  AY ;  Самостоятельная . X  : =  AZ ;  T  : =  ATime ; Самостоятельная . Размерность  : =  de4D ; конец ;конструктор  PointClass . CreateCopy ( APoint :  PointClass ) ; begin  // реализация "копирующего" конструктора  APoint . X  : =  AX ;  А.Пункт . Y  : =  AY ;  APoint . X  : =  AZ ;  А.Пункт . T  : =  ATime ; Самостоятельная . Размерность  : =  de4D ; конец ;деструктор  PointClass . PointClass . Уничтожить ; begin  // реализация универсального деструктора без аргументов  Self . Размерность  : =  deUnAssigned ; конец ;var  (* переменная для статического размещения *)  S :  PointClass ;  (* переменная для динамического размещения *)  D :  ^ PointClass ;начать  (* из программы *)  (* объект спасательный круг со статическим распределением *)  S . Создать ( 5 ,  7 ) ; (* сделайте что-нибудь с "S" *) S . Уничтожить ;  (*  линия жизни объекта с динамическим размещением *) D  =  new  PointClass ,  Create ( 5 ,  7 ) ; (* сделайте что-нибудь с "D" *) избавиться от  D ,  уничтожить ; конец .  (* программы *)

Python [ править ]

class  Socket :  def  __init__ ( self ,  remote_host :  str )  ->  None :  # подключиться к удаленному хосту def  send ( self ):  # Отправить данные def  recv ( self ):  # Получение данных  def  close ( self ):  # закрываем сокет  def  __del__ ( self ):  # __del__ волшебная функция, вызываемая, когда счетчик ссылок объекта равен нулю  self . закрыть ()def  f ():  socket  =  Socket ( "example.com" )  сокет . send ( "test" )  возвратный  сокет . recv ()

Сокет будет закрыт на следующем этапе сборки мусора после выполнения и возврата функции "f", поскольку все ссылки на него были потеряны.

См. Также [ править ]

  • Приобретение ресурсов - это инициализация (RAII), подход к управлению ресурсами путем привязки их к времени жизни объекта.

Заметки [ править ]

  1. ^ Есть разные тонкости; например, в C ++ статические локальные переменные создаются детерминированно при первом вызове их функции, но уничтожение не является детерминированным.
  2. ^ В C ++ создание статических локальных переменных происходит детерминированно аналогичным образом: когда выполнение достигает объявления в первый раз.

Ссылки [ править ]

  1. ^ C ++ FAQ: Что такое «Идиома именованного конструктора»?