stringtranslate.com

Время жизни объекта

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

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

Обзор

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

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

Детерминизм

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

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

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

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

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

Шаги

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

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

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

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

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

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

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

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

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

Программирование на основе классов

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

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

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

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

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

Управление ресурсами

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

Создание объекта

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

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

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

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

Шаблон абстрактной фабрики — это способ отделить конкретную реализацию объекта от кода создания такого объекта.

Методы создания

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

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

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


gg\gY:, и +newPointWithR:andTheta:. В C++ нечто подобное можно сделать, используя статические функции-члены. [1]

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

Уничтожение объекта

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

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

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

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

Примеры

С++

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

Джава

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 ); // вызов конструктора копирования // сбор мусора происходит под прикрытием, а объекты уничтожаются } }                          

С#

пространство имен 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 ) { var defaultfoo = new Foo (); // Вызов конструктора по умолчанию var foo = new Foo ( 14 ); // Вызов первого конструктора var foo2 = new Foo ( 12 , 16 ); // Вызов перегруженного конструктора var foo3 = new Foo ( foo ); // Вызов конструктора копирования } }                                                 

Цель-C

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

Объектный Паскаль

Родственные языки: «Delphi», «Free Pascal», «Mac Pascal».

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

Питон

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" )  сокет . отправить ( «тест» )  вернуть  сокет . получение ()

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

Смотрите также

Примечания

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

Рекомендации

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