Životní cyklus objektů

Z Wikipedie, otevřené encyklopedie

V objektově orientovaném programování (OOP) je životnost objektu (neboli životní cyklus) objektu charakterizován časem mezi vytvořením objektu a jeho zničením. Pravidla pro životnost objektů se mezi programovacími jazyky výrazně liší, v některých případech se životnost neliší implementací daného jazyka, ale může se lišit tím, jak jen implementováno v daném řešení programu.

V některých případech se doba životnosti objektu shoduje s dobou objektu, pro kterou je tento objekt určitou hodnotou v proměnné (jako statické proměnné, tak i pro automatické proměnné), ale v běžném životě není existence jednoho objektu vázaná pouze na životnost jednoho objektu. V mnoha případech u většiny dnešních objektově orientovaných jazyků, zejména těch, které užívají určitý nástroj pro správu paměti (například v Javě – Garbage collector, nebo ve SwiftARC – Automatic Reference Counting), jsou objekty spravovány v haldě a jejich životnost není určena životností pouze jednoho objektu, ale ve skutečnosti doba odpovídá existenci alespoň jednoho odkazuju, který se váže k tomuto objektu.

Při zničení jednoho objektu, na který se váže jiný objekt, nejsou smazány oba objekty zároveň, ale pouze odkaz mezi těmito objekty.

Silné a slabé reference[editovat | editovat zdroj]

Pro možnost definování, zda daná reference je pro daný objekt životně kritická, je často možné definovat na základě dvou typů referencí a to silných (angl. strong references) a slabých (angl. weak references). Pro upřesnění, doba existence objektu, neboli doba po kterou je objekt udržován v paměti je závislá na existenci alespoň jednoho silného odkazu na tento objekt. Pokud již není žádná taková reference, je to indikátor pro správce paměti, že tento objekt není už dále  potřeba uchovávat v paměti.

S životností a možností reference je také úzce spjat rozsah působnosti.

class Foo {}
var newFoo = Foo() // Vytvoření nového objektu

/*
Přiřazení silné reference na objekt do proměnné. 
Silná reference se vytváří defaultně.
*/
var strongReference = newFoo    
weak var weakReference = newFoo // Přiřazení slabé reference na objekt.

Rozsah působnosti[editovat | editovat zdroj]

Pod rozsahem působnosti objektů se skrývá časový úsek, ve kterém je objekt přístupný v rámci určité části programu.

Na rozdíl od životnosti může objekt existovat v paměti, ale nemusí být přístupný. Rozsah působení v podstatě definuje určitou oblast programu, ve kterém je objekt viditelný jiným objektům.

V objektově programovacích jazycích jsou často rozsah působnosti rozdělen do dvou kategoriích a to nejvyšší úrovně (neboli globální) a vnořené (lokální).

Globální rozsah působnosti[editovat | editovat zdroj]

Obecně objekty jakožto proměnné, konstanty a další programovací konstrukce, které jsou deklarované na nejvyšší úrovni zdrojového souboru jsou přístupné v jakémkoliv jiném zdrojovém souboru v rámci stejného modulu. Na nejvyšší úrovni zdrojového souboru je deklarováno vše, co není deklarované uvnitř nějakého bloku kódu.

Je však potřeba poznamenat, že se jedná často o výchozí chování, které může být upraveno pomocí modifikátorů přístupu.

Modifikátory přístupu se u jednotlivých objektových programovacích jazyků z větší části často shodují, ale mají i své unikátní modifikátory.

Lokální rozsah působnosti[editovat | editovat zdroj]

Lokální, neboli vnořený rozsah, je podmnožinou globálního rozsahu a je obecně formován každou novou organizační jednotkou (modulem, zdrojový souborem, třídou a nebo blokem kódu). Každá jednotka má svůj rozsah působnosti. Zdrojový soubor vytváří nový rozsah působnosti. Třída vytváří svůj rozsah a nebo také i funkce, či metoda.

V modulech jsou obsaženy zdrojové soubory programu. Tyto soubory se tedy nachází v jeho lokálním rozsahu působnosti, pro které je daný modul jakýmsi globálním rozsahem působnosti.

Každý objekt je deklarovaný u určitém místě hierarchie působností. Hierarchie určuje jaké objekty jsou přístupné (viditelné) pro jiné objekty. Stanovuje, že objekty deklarované na určité úrovni, jsou viditelné ostatním objektům, které jsou deklarované buď na stejné nebo vyšší úrovni hierarchie.

Obecně platí, že objekty vzniklé v prostoru určitého rozsahu působnosti objektu, také zanikají společně s objektem tvořící tento rozsah působnosti. Pokud tedy během životnosti objektu tvořící tento rozsah působnosti nenavázali jinou silnou referenci s jiným objektem.

// Rozsah působnosti třídy Foo.
class Foo {
    // Rozsah působnosti metody `greeting`. Je definovanám třídou Foo. 
    func greeting() {
        // Rozsah působnosti proměné `text`. 
        var text = "Hello there!" // Lokální proměnná metody `greeting` 
        print(text)
    }
}

Přehled[editovat | editovat zdroj]

Zatímco základní myšlenka životnosti objektu je jednoduchá – objekt je vytvořen, použit a poté zničen – detaily se mezi jazyky značně liší a v rámci implementací daného jazyka jsou úzce spjaty s implementací nástrojů pro správu paměti. Kromě toho se mezi jednotlivými kroky a mezi koncepcemi na úrovni jazyka a koncepcemi na úrovni implementace jeví mnoho jemných rozdílů. Terminologie je relativně standardní, ale které kroky odpovídají danému termínu se značně liší mezi jazyky.

Pojmy obvykle přicházejí v dvojicích antonym, jeden pro koncepci vytvoření a jeden pro odpovídající koncept destrukce, jako jsou inicializace / finalizace, konstrukce / destrukce nebo inicializace / ukončení (termination).

Dále se s tímto procesem mohou analogicky spojovat i termíny se správou paměti jako alokace / dealokace, i když  tvorba a zanikání objektů může zahrnovat podstatně více než jen alokaci a uvolnění z paměti.

Důležitým rozdílem u životnosti je, zda životnost objektu je deterministická nebo non-deterministická. Tato vlastnost může být rozdílná u jazyků a to hlavně na základně principu, jak probíhají procesy s alokací paměti pro objekty.

Například druh proměnné určuje v jakém pořadí budou alokovány do paměti. Proto je možné k některým proměnným přistupovat v určitém bloku kódu a k některým ještě ně, jelikož nemají zatím přiřazenou žádnou adresu v paměti. Objekty alokované se statickou alokací paměti, tedy objekty uložené ve statických proměnných, nebo objekty definované na globální úrovni mají nedeterministický přístup. Může se říci, že jejich životnost se zdá být shodná s dobou spuštění programu. Pořadí vytvoření a zničení, který statický objekt je vytvořen jako první a který druhý, atd. je obecně nedeterministický.[pozn. 1]

U objektů s automatickým přidělováním paměti nebo dynamickým přidělováním paměti se vytváření objektů obecně provádí deterministicky, buď explicitně, když je objekt explicitně vytvořen (například prostřednictvím new v jazyce C++ nebo Java), nebo implicitně na začátku životnosti proměnné, zejména pokud je v bloku kódu zadána automatická proměnná, například při deklaraci. Zničení objektu se liší, nicméně – v některých jazycích, zejména v C++, jsou automatické a dynamické objekty zničeny v deterministických časech, jako je například výstup z bloku kódu (rozsahu působnosti), explicitní destrukce (prostřednictvím ruční správy paměti – zavoláním například v jazyce Swift metodu objektu deinit() pro destrukci objektu, nebo existencí reference odkazující na daný objekt.

V jazycích využívající pro správu paměti nástroj Garbage-collector jsou obecně objekty dynamicky spravovány v haldě, dokonce i tehdy pokud jsou prvotně vázány k automatické proměnné. Na rozdíl od automatických proměnných s primitivními hodnotami, které jsou typicky automaticky přiděleny do zásobníku, nebo v registru. To umožňuje, aby byl objekt vrácen z funkce, jako návratová hodnota, aniž by byl zničen.

V některých případech je však možná optimalizace kompilátoru, totiž provedení analýzy úniku a dotazování, že únik není možný, a tudíž objekt může být přidělen na svazek. Toto je významné v Javě. V takovém případě dojde ke zničení objektu okamžitě – možná i během životnosti proměnné (před koncem jejího rozsahu), pokud je nedosažitelný.

Komplexním případem je použití fondu objektů, kde mohou být objekty vytvořeny předem, nebo znovu použity a tak zjevné vytvoření a zničení nemusí odpovídat skutečnému vytvoření a zničení objektu. Jednalo by se o (re)inicializaci již vytvořeného objektu. V tomto případě může být stvoření i zničení nedeterministické.

Kroky[editovat | editovat zdroj]

Vytváření objektů lze rozdělit do dvou operací: alokace paměti a inicializace , kde inicializace zahrnuje přiřazení hodnot do polí objektů a případné spuštění libovolného jiného kódu. Jedná se o koncept úrovni implementace, kde jsou jemné rozdíly s rozlišováním deklarace a definice proměnné. Pro objekt, který je vázán na proměnnou, může být deklarace vyjádřena jako provedení alokace v paměti (vyhrazení místa pro objekt) a definice samotným přiřazením hodnoty. Informace o deklaraci jsou spíše použity pouze pro kompilátor (např. Překlad názvů), neodpovídá přímo zkompilovanému kódu.

Podobně i zánik objekt může být rozdělen do dvou kroků a to finalizace a uvolnění z paměti (dealokace). Analogicky lze zničení objektu rozdělit do dvou operací, v opačném pořadí: finalizace a uvolnění z paměti. Tyto proměnné nemají analogické koncepty na úrovni jazyka pro proměnné: proměnná životnost končí implicitně (pro automatické proměnné – odebráním ze zásobníku, pro statické proměnné – ukončením programu) a v této době (nebo později v závislosti na implementaci) je paměť uvolněna, ale žádná finalizace se obecně neděje. Když je však životnost objektu vázána na životnost proměnné, konec životnosti proměnné způsobí dokončení objektu; toto je standardní paradigma v C++.

Tyto výsledky společně přinášejí čtyři kroky na úrovni implementace:

  • alokace,
  • inicializace
  • destrukce (finalizace)
  • dealokace (uvolnění)

Tyto kroky mohou být prováděny automaticky jazykovým modulem runtime, interpretrem nebo virtuálním strojem, nebo může být programátorem specifikován ručně v podprogramu, konkrétně pomocí metod – četnost těchto změn se mezi jednotlivými kroky a jazyky výrazně liší. Inicializace je velmi běžně programátorem specifikovaná ve třídních jazycích, zatímco v striktních prototypových jazycích se inicializace automaticky provádí kopírováním. Dokončení je také velmi obyčejné v jazycích s deterministickou destrukcí, pozoruhodně C++, ale hodně méně obyčejný v garbage-collected jazyky. Přidělení je více zřídka specifikováno a deallocation obecně nemůže být specifikován.

Stavy během vytváření a ničení objektů[editovat | editovat zdroj]

Důležitá precisnost musí být definována pro jednotlivé stavy objektu během vytváření, ničení nebo zpracování případů, kdy dochází k chybám nebo výjimkám. Výslovně řečeno, životnost objektu začíná, jakmile se alokace dokončí a končí, když začíná uvolňování z paměti. Během inicializace a finalizace je tedy objekt živý, ale nemusí být v konzistentním stavu. Zajištění konzistence třídy je klíčovou součástí inicializace a musí být udržován během doby, která začíná v bodě dokončení inicializace, a končí při započatí finalizace. Během této doby se očekává, že objekt je živý a v konzistentním stavu.

Pokud selhává tvorba nebo zničení, hlášení chyb (často vyvoláním výjimky) může být složité: objekt nebo související objekty mohou být v nekonzistentním stavu av případě zničení – což se obecně děje implicitně, a tedy v nespecifikovaném prostředí – může být obtížné zpracovávat chyby. Opačný problém – příchozí výjimky, ne odchozí výjimky – je to, zda by se mělo vytvořit nebo zničit jinak, pokud se vyskytnou během zpracování výjimek, kdy může být žádoucí jiné chování.

Další subtilitou je, že k vytváření a destrukci dochází u statických proměnných, jejichž životnost se shoduje s dobou běhu programu – kdy dochází k tvorbě a destrukci během běžného provádění programu nebo ve speciálních fázích před a po pravidelném provedení – a jak jsou objekty zničeny v programu ukončením, kdy program nemusí být v obvyklém nebo konzistentním stavu. Jedná se zejména o problém, který se týká jazyků využívající Garbage-collector, protože při ukončení programu mohou mít spoustu odpadků.

Programování založené na třídě[editovat | editovat zdroj]

V objektovém programování je program definován objekty a jejich vzájemnou komunikací. Programovací konstrukcí pro vytváření objektů jsou třídy. Ty jsou definovány pro určitou skupinu objektů se společnými vlastnostmi. Jednotlivé objekty jsou vytvářeny specifickými metodami tzv. konstruktory. Vytvořený objekt je nazýván jako instancí dané třídy.

Zničení objektu probíhá naopak metodami zvanými destruktory. Tyto metody mohou být manuálně volány v případě, že chce zajistit jejich destrukci a nespoléhat se na zajištění jejich destrukce nástrojem pro správu paměti.

Tvorba a zničení objektu jsou tedy nazývané konstrukce a destrukce.

Jedním takovým jazykovým konstrukčním rozdílem mezi těmito speciálními metodami konstruktory a destruktory je, že konstruktory jsou metody třídy a destruktory jsou instanční metody. Destruktor je volán již pro konkrétní objet, zatímco konstruktor nám tento objekt teprve vytvoří.

V běžném používání je konstruktor metodou přímo volanou explicitně uživatelským kódem pro vytvoření objektu, zatímco destruktor je podprogram definovaný (obvykle implicitně, ale někdy explicitně) na zničení objektů v jazycích s deterministickou životností objektů.

#Swift

class Foo {
    
    /// Třídní metoda - konstruktor
    init() { }
    
    /// Instanční metoda - destruktor
    deinit() {}
}

var newFoo = Foo() // Inicializace objektu 
newFoo.deinit() // Manuální destrukce objektu

Poznámky[editovat | editovat zdroj]

  1. There are various subtleties; for example in C++, static local variables are deterministically created when their function is first called, but destruction is non-deterministic.

Reference[editovat | editovat zdroj]

V tomto článku byl použit překlad textu z článku Object lifetime na anglické Wikipedii.