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

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

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

Обзор

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

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

Детерминизм

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

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

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

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

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

Шаги

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

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

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

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

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

Статус при создании и уничтожении

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Разрушение объекта

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

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

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

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

Примеры

C ++

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

Ява

учебный класс Фу{    общественный Фу(int Икс)    {        // Это реализация        // конструктор с одним аргументом    }    общественный Фу(int Икс, int у)    {        // Это реализация        // конструктор с двумя аргументами    }    общественный Фу(Фу Старый)    {        // Это реализация        // конструктор копирования    }    общественный статический пустота главный(Нить[] аргументы)    {        Фу фу = новый Фу(14); // вызов первого конструктора        Фу foo2 = новый Фу(12, 16); // вызов перегруженного конструктора        Фу foo3 = новый Фу(фу); // вызываем конструктор копирования        // сборка мусора происходит под прикрытием, а объекты уничтожаются    }}

C #

пространство имен ObjectLifeTime {учебный класс Фу{    общественный Фу()    {        // Это реализация        // конструктор по умолчанию.    }    общественный Фу(int Икс)    {        // Это реализация        // конструктор с одним аргументом.    }     ~Фу()    {        // Это реализация        // деструктор.    }     общественный Фу(int Икс, int у)    {        // Это реализация        // конструктор с двумя аргументами.    }     общественный Фу(Фу Старый)    {        // Это реализация        // конструктор копирования.    }     общественный статический пустота Главный(нить[] аргументы)    {        Фу defaultfoo = новый Фу(); // Вызов конструктора по умолчанию        Фу фу = новый Фу(14); // Вызов первого конструктора        Фу foo2 = новый Фу(12, 16); // Вызов перегруженного конструктора        Фу foo3 = новый Фу(фу); // Вызов конструктора копирования    }}}

Цель-C

#import @интерфейс Точка : Объект{   двойной Икс;   двойной у;}// Это методы класса; мы объявили два конструктора+ (Точка *) newWithX: (двойной) Энди: (двойной);+ (Точка *) newWithR: (двойной) andTheta: (двойной);// Методы экземпляра- (Точка *) setFirstCoord: (двойной);- (Точка *) setSecondCoord: (двойной);/ * Так как Point является подклассом универсального Object  * class, мы уже получаем общее распределение и инициализацию * методы, + alloc и -init. Для наших конкретных конструкторов * мы можем сделать это с помощью этих методов, у нас есть * по наследству. */@конец @выполнение Точка- (Точка *) setFirstCoord: (двойной) new_val{   Икс = new_val;}- (Точка *) setSecondCoord: (двойной) new_val{   у = new_val;}+ (Точка *) newWithX: (двойной) x_val Энди: (двойной) y_val{   // Лаконично написанный метод класса для автоматического выделения и    // выполнить определенную инициализацию.   возвращаться [[[Точка выделить] setFirstCoord:x_val] setSecondCoord:y_val]; }+ (Точка *) newWithR: (двойной) r_val andTheta: (двойной) theta_val{   // Вместо того, чтобы делать то же самое, что и выше, мы можем коварно   // используем тот же результат, что и предыдущий метод   возвращаться [Точка newWithX:r_val Энди:theta_val];}@конецintглавный(пустота){   // Строит две точки p и q.   Точка *п = [Точка newWithX:4.0 Энди:5.0];   Точка *q = [Точка newWithR:1.0 andTheta:2.28];   //... текст программы ....      // Мы закончили с p, скажем, освободим его.   // Если p выделяет себе больше памяти, может потребоваться   // переопределить свободный метод объекта, чтобы рекурсивно   // освобождает память p. Но это не так, поэтому мы можем просто   [п свободный];   //... больше текста ...   [q свободный];   возвращаться 0;}

Object Pascal

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

программа Пример;тип  DimensionEnum =    (      deUnassigned,      de2D,      de3D,      de4D    );  PointClass = учебный класс  частный    Измерение: DimensionEnum;  общественный    Икс: Целое число;    Y: Целое число;    Z: Целое число;    Т: Целое число;  общественный    (* прототип конструкторов *)    конструктор Создавать();    конструктор Создавать(ТОПОР, AY: Целое число);    конструктор Создавать(ТОПОР, AY, Аризона: Целое число);    конструктор Создавать(ТОПОР, AY, Аризона, Время: Целое число);    конструктор CreateCopy(Точка: PointClass);    (* прототип деструкторов *)    деструктор Разрушать;  конец;конструктор PointClass.Создавать();начинать  // реализация универсального конструктора без аргументов  Себя.Измерение := deUnassigned;конец;конструктор PointClass.Создавать(ТОПОР, AY: Целое число);начинать  // реализация конструктора с двумя аргументами  Себя.Икс := ТОПОР;  Y := AY;  Себя.Измерение := de2D;конец;конструктор PointClass.Создавать(ТОПОР, AY, Аризона: Целое число);начинать  // реализация конструктора с 3 аргументами  Себя.Икс := ТОПОР;  Y := AY;  Себя.Икс := Аризона;  Себя.Измерение := de3D;конец;конструктор PointClass.Создавать(ТОПОР, AY, Аризона, Время: Целое число);начинать  // реализация конструктора с 4 аргументами  Себя.Икс := ТОПОР;  Y := AY;  Себя.Икс := Аризона;  Т := Время;  Себя.Измерение := de4D;конец;конструктор PointClass.CreateCopy(Точка: PointClass);начинать  // реализация конструктора "копировать"  Точка.Икс := ТОПОР;  Точка.Y := AY;  Точка.Икс := Аризона;  Точка.Т := Время;  Себя.Измерение := de4D;конец;деструктор PointClass.PointClass.Разрушать;начинать  // реализация универсального деструктора без аргументов  Себя.Измерение := deUnAssigned;конец;вар  (* переменная для статического распределения *)  S:  PointClass;  (* переменная для динамического размещения *)  D: ^PointClass;начинать (* программы *)  (* линия жизни объекта со статическим размещением *)  S.Создавать(5, 7);  (* сделайте что-нибудь с "S" *)  S.Разрушать;   (* линия жизни объекта с динамическим размещением *)  D = новый PointClass, Создавать(5, 7);  (* сделайте что-нибудь с "D" *)  избавляться D, Разрушать;конец.  (* программы *)

Python

учебный класс Разъем:    def __в этом__(себя, удаленный узел: ул) -> Никто:        # подключиться к удаленному хосту    def Отправить(себя):        # Отправить данные    def получить(себя):        # Получать данные            def Закрыть(себя):        # закрываем сокет            def __del__(себя):        # __del__ волшебная функция, вызываемая, когда счетчик ссылок объекта равен нулю        себя.Закрыть()def ж():    разъем = Разъем("example.com")    разъем.Отправить("тест")    возвращаться разъем.получить()

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

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

Примечания

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

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