„Kdy se sejdeme?“ – „Zítra ve tři.“ „Kdy je ta
schůzka?“ – „Příští měsíc.“ Pro běžný život jsou takové
údaje o čase zcela postačující. Jenže zkuste totéž v programování a
rychle zjistíte, že jste vstoupili do bludiště plného nástrah a
neočekávaných překvapení.
Čas v programování je jako šelma, která vypadá krotce, dokud na ni
nešlápnete. A jednou z nejmocnějších lstí této šelmy je letní čas a
jeho zákeřné přechody. Systém, který měl údajně ušetřit svíčky,
dnes způsobuje programátorům bezesné noci (pravděpodobně kolem 2:30 ráno,
kdy najednou zjistí, že jejich servery dělají podivné věci).
Vydejme se na průzkum temných zákoutí přechodů na letní čas a zpět,
jak je PHP (ne)zvládá a jak jsem se pokusil napravit toto šílenství
v Nette Utils. Připravte se na momenty, kdy 1 + 1 ≠ 2 a kdy přidání
delšího času vám paradoxně vrátí dřívější hodinu. Tohle by
nevymyslel ani Einstein.
Nejprve si prosvištíme
některá slovíčka
Než se ponoříme do problematiky, vysvětleme si několik
klíčových pojmů:
UTC (Coordinated Universal Time) – koordinovaný světový čas,
základní časový standard, od kterého se odvozují všechny ostatní
časové zóny. Je to v podstatě „nulový bod“ pro měření času na
celém světě.
Časový posun (offset) – kolik hodin je potřeba přičíst nebo
odečíst od UTC, abychom dostali místní čas. Označuje se jako UTC+X
nebo UTC-X.
CET (Central European Time) – středoevropský čas, který
používáme v zimě. Má posun UTC+1, což znamená, že když je v UTC
poledne, u nás je 13:00.
CEST (Central European Summer Time) – středoevropský letní
čas, který používáme v létě. Má posun UTC+2, takže když je v UTC
poledne, u nás je 14:00.
ČEST – komunistický pozdrav, něco, co patří doufám už pouze
do starý časů
Letní čas – systém, kdy v určité části roku (obvykle
v létě) posuneme hodiny o hodinu dopředu, abychom lépe využili denní
světlo.
Ten okamžik trval celý
světelný rok
Pojďme si sekundu po sekundě rozebrat, jak probíhá přechod na letní
čas a zpátky. Jako příklad si vezměme nedávnou změnu času v České
republice v neděli 30. března 2025:
Představte si, že by vaše PHP objekty mohly být čistší,
přehlednější a lépe použitelné. Dobrá zpráva – už nemusíte snít!
PHP 8.4 přichází s revoluční novinkou v podobě property hooks a
asymetrické viditelnosti, které kompletně mění pravidla hry
v objektově orientovaném programování. Zapomeňte na neohrabané gettery a
settery – konečně máme k dispozici moderní a intuitivní způsob, jak
kontrolovat přístup k datům objektů. Pojďme se podívat na to, jak tyto
novinky mohou změnit váš kód k nepoznání.
Property hooks představují promyšlený způsob, jak definovat chování
při čtení a zápisu vlastností objektu – a to mnohem čistěji a
výkonněji než dosavadní magické metody __get/__set. Je to jako
byste dostali k dispozici sílu magických metod, ale bez jejich typických
nevýhod.
Podívejme se na jednoduchý příklad z praxe, který vám ukáže, proč
jsou property hooks tak užitečné. Představme si běžnou třídu
Person s veřejnou property age:
class Person
{
public int $age = 0;
}
$person = new Person;
$person->age = 25; // OK
$person->age = -5; // OK, ale to je přece nesmysl!
PHP sice díky typu int zajistí, že věk bude celé číslo
(to lze od PHP 7.4), ale co s tím záporným věkem? Dříve bychom museli
sáhnout po getterech a setterech, property by musela být private, museli
bychom doplnit spoustu kódu… S hooks to vyřešíme elegantně:
class Person
{
public int $age = 0 {
set => $value >= 0 ? $value : throw new InvalidArgumentException;
}
}
$person->age = -5; // Ups! InvalidArgumentException nás upozorní na nesmysl
Krása tohoto řešení spočívá v jeho jednoduchosti – navenek se
property chová úplně stejně jako dřív, můžeme číst i zapisovat
přímo přes $person->age. Ale máme plnou kontrolu nad tím,
co se při zápisu děje. A to je teprve začátek!
Můžeme jít ještě dál a vytvořit třeba hook pro čtení. Hookům lze
přidat atributy. A samozřejmě mohou obsahovat složitější logiku než
jednoduchý výraz. Podívejte se na tento příklad práce se jménem:
class Person
{
public string $first;
public string $last;
public string $fullName {
get {
return "$this->first $this->last";
}
set(string $value) {
[$this->first, $this->last] = explode(' ', $value, 2);
}
}
}
$person = new Person;
$person->fullName = 'James Bond';
echo $person->first; // vypíše 'James'
echo $person->last; // vypíše 'Bond'
A něco důležitého: kdykoliv se přistupuje k proměnné (i uvnitř
samotné třídy Person), vždy se využijí hooks. Jediná výjimka je přímý
přístup k reálné proměnné uvnitř kódu samotného hooku.
Ohlédnutí do
minulosti: Co nás naučil SmartObject?
Pro uživatele Nette může být zajímavé ohlédnout se do minulosti.
Framework totiž podobnou funkcionalitu nabízel už před 17 lety ve
formě SmartObject,
který výrazně vylepšoval práci s objekty v době, kdy PHP v této
oblasti značně zaostávalo.
Pamatuju si, že tehdy přišla vlna bezbřehého nadšení, kdy se
properties používaly prakticky všude. Tu pak vystřídala vlna opačná –
nepoužívat je nikde. Důvod? Chybělo jasné vodítko, kdy je lepší použít
metody a kdy property. Ale dnešní nativní řešení je kvalitativně úplně
jinde.Property hooks a asymetrická viditelnost jsou plnohodnotné nástroje,
které nám dávají stejnou úroveň kontroly jako máme u metod. Proto dnes
můžeme mnohem lépe rozlišit, kdy je property skutečně tím správným
řešením.
Představte si, že byste mohli svým datům dát pevnou půdu pod
nohama – jednou je nastavíte a pak si můžete být jistí, že je nikdo
nezmění. Přesně to přineslo PHP 8.1 s readonly vlastnostmi. Je to jako
dát vašim objektům neprůstřelnou vestu – chrání jejich data před
nechtěnými změnami. Pojďme se podívat, jak vám tento mocný nástroj
může usnadnit život a na co si při jeho používání dát pozor.
Začněme jednoduchým příkladem:
class User
{
public readonly string $name;
public function setName(string $name): void
{
$this->name = $name; // První nastavení - vše OK
}
}
$user = new User;
$user->setName('John'); // Paráda, máme jméno
echo $user->name; // "John"
$user->setName('Jane'); // BOOM! Výjimka: Cannot modify readonly property
Jakmile jednou jméno nastavíte, je to jako vytesané do kamene. Žádné
náhodné přepsání, žádné nechtěné změny.
Kdy je uninitialized
opravdu uninitialized?
Často se setkávám s mýtem, že readonly vlastnosti musí být nastaveny
v konstruktoru. Ve skutečnosti je PHP mnohem flexibilnější – můžete je
inicializovat kdykoliv během života objektu, ale pouze jednou! Před prvním
přiřazením jsou ve speciálním stavu ‚uninitialized‘, což je takový
limbo stav mezi nebytím a bytím.
A tady přichází zajímavý detail – readonly vlastnosti nemohou mít
výchozí hodnotu. A proč? Kdyby měly výchozí hodnotu, staly by se de facto
konstantami – hodnota by byla nastavena při vytvoření objektu a už by
nešla změnit.
Vyžadují se typy
Readonly proměnné vyžadují explicitní definici datového typu. Je to
proto, že stav ‚uninitialized‘, který využívají, existuje pouze
u typovaných proměnných. Bez uvedení typu tedy readonly proměnnou nelze
definovat. Pokud si nejste jistí typem, můžete použít
mixed.
Vývoj softwaru často přináší dilema. Například jak
řešit situace, kdy getter nemá co vrátit. V tomto článku prozkoumáme
tři strategie pro implementaci getterů v PHP, které ovlivňují strukturu a
čitelnost kódu, a každá má své specifické výhody i nevýhody. Pojďme
se na ně podrobněji podívat.
Univerzální getter
s parametrem
Prvním a v Nette používaným řešením je vytvoření jediné getter
metody, která, pokud hodnota není dostupná, může dle potřeby vrátit buď
null nebo vyhodit výjimku. O chování rozhoduje volitelný
parametr. Zde je příklad, jak by mohla metoda vypadat:
public function getFoo(bool $need = true): ?Foo
{
if (!$this->foo && $need) {
throw new Exception("Foo not available");
}
return $this->foo;
}
Hlavní výhodou tohoto přístupu je, že eliminuje potřebu mít několik
verzí getteru pro různé scénáře použití. Někdejší nevýhodou byla
horší srozumitelnost uživatelského kódu používajícího booleovské
parametry, ale ta padla s příchodem pojmenovaných parametrů, kdy lze psát
getFoo(need: false).
Dále tento přístup může způsobit komplikace v oblasti statické
analýzy, jelikož dle signatury se zdá, že getFoo() může
vrátit null v každé situaci. Nicméně nástroje jako PHPStan
umožňují explicitní dokumentaci chování metody pomocí speciálních
anotací, které zlepšují porozumění kódu a jeho správnou analýzu:
/** @return ($need is true ? Foo : ?Foo) */
public function getFoo(bool $need = true): ?Foo
{
}
Tato anotace jasně určuje, jaké návratové typy může metoda
getFoo() generovat v závislosti na hodnotě parametru
$need. Ale například PhpStorm jí nerozumí.
Dvojice metod: hasFoo()
a getFoo()
Další možností je rozdělit zodpovědnost na dvě metody:
hasFoo() pro ověření existence hodnoty a getFoo()
pro její získání. Tento přístup zvyšuje přehlednost kódu a je
intuitivně srozumitelný.
public function hasFoo(): bool
{
return (bool) $this->foo;
}
public function getFoo(): Foo
{
return $this->foo ?? throw new Exception("Foo not available");
Hlavním problémem je redundance, zvláště v případech, kdy je kontrola
dostupnosti hodnoty sama o sobě náročným procesem. Pokud
hasFoo() provádí složité operace k ověření, zda je hodnota
dostupná, a tato hodnota je poté opět získávána pomocí
getFoo(), dojde k jejich opětovnému provedení. Hypoteticky
může být stav objektu nebo dat změněn mezi voláním hasFoo()
a getFoo(), což může vést k nesrovnalostem. Z uživatelského
pohledu může být tento přístup méně pohodlný, protože nás nutí volat
dvojici metod s opakujícím se parametrem. A nemůžeme využít například
null-coalescing operátor.
Výhodou je, že některé nástroje pro statickou analýzu umožňují
definovat pravidlo, že po úspěšném volání hasFoo() nedojde v
getFoo() k vyhození výjimky.
Metody getFoo() a
getFooOrNull()
Třetí strategií pro je rozdělení funkcionality na dvě metody:
getFoo() pro vyhození výjimky, pokud hodnota neexistuje, a
getFooOrNull() pro vrácení null. Tento přístup
minimalizuje redundanci a zjednodušuje logiku.
public function getFoo(): Foo
{
return $this->getFooOrNull() ?? throw new Exception("Foo not available");
}
public function getFooOrNull(): ?Foo
{
return $this->foo;
}
Alternativou je dvojice getFoo() a
getFooIfExists(), ale v tomto případě nemusí být zcela
intuitivní pochopit, která metoda vyhazuje výjimku a která vrací
null. O trošku výstižnější by byla dvojice
getFooOrThrow() a getFoo(). Další možností je
getFoo() a tryGetFoo().
Každý z představených přístupů k implementaci getterů v PHP má
své místo v závislosti na specifických potřebách projektu a preferencích
vývojářského týmu. Při výběru vhodné strategie je důležité zvážit,
jaký dopad bude mít na čitelnost, údržbu a výkon aplikace. Volba by
odrážet snahu o co nejsrozumitelnější a nejefektivnější kód.
Chcete se ponořit do světa objektově orientovaného
programování v PHP, ale nevíte, kde začít? Mám pro vás nového
stručného průvodce OOP, který vás seznámí se všemi těmi pojmy, jako
class, extends, private atd.
Průvodce si neklade za cíl udělat z vás mistry v psaní čistého kódu
nebo podat zcela vyčerpávající informace. Jeho cílem je vás rychle
seznámit se základními koncepty OOP v současném PHP a dát vám fakticky
správné informace. Tedy poskytnout pevný základ, na kterém můžete dále
stavět. Třeba aplikace v Nette.
Jako navazující čtení doporučuji podrobný průvodce
světem správného návrhu kódu. Ten je přínosný i pro všechny, co
PHP a objektově orientované programování ovládají.
Programování v jazyce PHP byla vždycky trošku výzva, ale naštěstí
prošlo mnohými změnami k lepšímu. Pamatujete na časy před verzí PHP 7,
kdy skoro každá chyba znamenala fatal error, což aplikaci okamžitě
ukončilo? V praxi to znamenalo, že jakákoli chyba mohla aplikaci zcela
zastavit, aniž by programátor měl možnost ji zachytit a náležitě na ni
reagovat. Nástroje jako Tracy využívaly magických triků, aby dokázaly
takové chyby vizualizovat a logovat. Naštěstí s příchodem PHP 7 se tohle
změnilo. Chyby nyní vyvolávají výjimky, jako jsou Error, TypeError a
ParseError, které lze snadno zachytávat a ošetřit.
Avšak i v moderním PHP existuje slabé místo, kdy se chová stejně jako
ve své páté verzi. Mluvím o chybách během kompilace. Ty nelze zachytit a
okamžitě vedou k ukončení aplikace. Jedná se o chyby úrovně
E_COMPILE_ERROR. PHP jich generuje na dvě stovky. Vzniká paradoxní situace,
že když v PHP načteme soubor se syntaktickou chybou, což může být třeba
chybějící středník, vyhodí zachytitelnou výjimku ParseError. Ovšem
v případě, že kód je sice syntakticky v pořádku, leč obsahuje chybu
odhalitelnou až při kompilaci (například dvě metody se stejným názvem),
vyústí to ve fatální chybu, kterou zachytit nelze.
Bohužel, kompilační chyby v PHP nemůžeme ověřit interně. Existovala
funkce php_check_syntax(), která navzdory názvu odhalovala
i kompilační chyby. Byla zavedena v PHP 5.0.0, ale záhy odstraněna ve
verzi 5.0.4 a od té doby nikdy nebyla nahrazena. Pro ověření správnosti
kódu se musíme spolehnout na linter z příkazové řádky:
php -l soubor.php
Z prostředí PHP lze ověřit kód uložený v proměnné
$code třeba takto:
Nicméně režie spouštění externího PHP procesu kvůli ověření
jednoho souboru je docela velká. Ale dobrá zpráva přichází s verzí PHP
8.3, která přinese možnost ověřovat více souborů najednou:
Jenže udělal to v době, kdy potřeba používat isset()
značně klesla. Dnes častěji počítáme s tím, že data, ke kterým
přistupujeme, existují. A pokud neexistují, tak se o tom sakra chceme
dozvědět.
Operátor ?? má ale vedlejší efekt a to schopnost detekovat
null. Což je taky nejčastější důvod k jeho užití:
$len = $this->length ?? 'default value'
Bohužel zároveň zatajuje chyby. Zatajuje překlepy:
Bylo by úžasné, kdyby PHP 9.0 mělo odvahu chování operátoru
?? upravit k trošku větší striktnosti. Udělat z „isset
operátoru“ opravdu „null coalesce operator“, jak se mimochodem oficiálně jmenuje.
Před mnoha lety jsem si uvědomil, že když v PHP ve funkci používám
proměnnou obsahující předdefinovanou tabulku dat, tak při každém volání
funkce musí být pole znovu „vytvořené“, což je překvapivě dost
pomalé. Příklad:
Zrychlení, pokud pole bylo trošku větší, se pohybovalo v několika
řádech (jako třeba klidně 500×).
Takže od té doby jsem u konstantních polí vždy používal
static. Je možné, že tento zvyk někdo následoval, a třeba ani
netušil, jaký má skutečný důvod. Ale to nevím.
Před pár týdny jsem psal třídu, která nesla v několika properties
velké tabulky předdefinovaných dat. Uvědomil jsem si, že to bude zpomalovat
vytváření instancí, tedy že operátor new bude pokaždé
„vytvářet“ pole, což jak víme je pomalé. Tudíž musím properties
změnit na statické, nebo možná ještě lépe použít konstanty.
A tehdy jsem si položil otázku: Hele a nejsi jen ve vleku cargo kultu? Opravdu pořád
platí, že bez static je to pomalé?
Těžko říct, PHP prošlo revolučním vývojem a staré pravdy nemusí
být platné. Připravil jsem proto testovací vzorek a udělal pár měření.
Samozřejmě jsem si potvrdil, že v PHP 5 použití static uvnitř funkce
nebo u properties přineslo zrychlení o několik řádů. Ale pozor, v PHP
7.0 už šlo jen o jeden řád. Výborně, projev optimalizací v novém
jádře, ale stále je rozdíl podstatný. Nicméně u dalších verzí PHP
rozdíl dál klesal a až postupně téměř vymizel.
Dokonce jsem zjistil, že použití static uvnitř funkce v PHP 7.1 a
7.2 běh zpomalovalo. Zhruba 1,5–2×, tedy z pohledu řádů, o kterých se
tu celou dobu bavíme, zcela zanedbatelně, ale byl to zajímavý paradox. Od
PHP 7.3 rozdíl zmizel zcela.
Zvyklosti jsou dobrá věc, ale je nutné jejich smysl stále validovat.
Zbytečný static v těle funkcí už používat nebudu. Nicméně u oné
třídy, která držela velké tabulky předdefinovaných dat v properties,
jsem si řekl, že je programátorsky správné konstanty použít. Za chvíli
jsem měl refaktoring hotový, ale už jak vznikal jsem naříkal nad tím, jak
se kód stává ošklivým. Místo $this->ruleToNonTerminal nebo
$this->actionLength se v kódu objevovalo řvoucí
$this::RULE_TO_NON_TERMINAL a $this::ACTION_LENGTH a
vypadalo to fakt hnusně. Zatuchlý závan ze sedmdesátých let.
Až jsem zaváhal, jestli vůbec chci koukat na tak hnusný kód, a jestli
raději nezůstanu u proměnných, případně statických proměnných.
A tehdy mi to došlo: Hele nejsi jen ve vleku cargo kultu?
No jasně že jsem. Proč by měla konstanta řvát? Proč by měla na sebe
upozorňovat v kódu, být vyčnívajícím elementem v toku programu? Fakt,
že struktura slouží jen ke čtení, není důvod PRO ZASEKNUTÝ CAPSLOCK,
AGRESIVNÍ TÓN A HORŠÍ ČITELNOST.
TRADICE VELKÝCH PÍSMEN POCHÁZÍ Z JAZYKA C, KDE SE TAKTO OZNAČOVALY
MAKROKONSTANTY PREPROCESORU. BYLO UŽITEČNÉ NEPŘEHLÉDNUTELNĚ ODLIŠIT KÓD
PRO PARSER OD KÓDU PRO PREPROCESOR. V PHP SE ŽÁDNÉ PREPROCESORY NIKDY
NEPOUŽÍVALY, TAKŽE NENÍ ANI DŮVOD psát konstanty velkými písmeny.
Ještě ten večer jsem je všude zrušil. A stále nemohl pochopil, proč
mě to nenapadlo už před dvaceti lety. Čím větší blbost, tím tužší
má kořínek.
Vždycky mi vadila jakákoliv nadbytečnost nebo duplicita v kódu. Už jsem
o tom psal před mnoha lety. Při
pohledu na tento kód prostě trpím:
interface ContainerAwareInterface
{
/**
* Sets the container.
*/
public function setContainer(ContainerInterface $container = null);
}
Obsahovou zbytečnost komentáře u metody ponechme stranou.
A protentokrát i projev nepochopení dependency injection, pokud knihovna
potřebuje disponovat takovým rozhraním. O tom, že použití slova
Interface v názvu rozhraní je pro změnu projevem nepochopení
objektového programování, chystám samostatný článek. Koneckonců jsem si
tím sám prošel.
Ale proč proboha uvádět viditelnost public? Vždyť je to pleonasmus. Kdyby to nebylo
public, tak to pak není rozhraní, ne? No a ještě někoho napadlo z toho
udělat „standard“ ?♂️
Uff, omlouvám se za dlouhý úvod, to, kam celou dobu směřuju, je zda
psát volitelné nullable typy s otazníkem nebo bez. Tj:
// bez
function setContainer(ContainerInterface $container = null);
// s
function setContainer(?ContainerInterface $container = null);
Osobně jsem se vždycky klonil k první možnosti, protože informace daná
otazníkem je redundantní (ano, oba zápisy znamenají z pohledu jazyka
totéž). Zároveň se tak zapisoval veškerý kód do příchodu PHP 7.1, tedy
verze, která otazník přidala, a musel by být dobrý důvod jej
najednou měnit.
S příchodem PHP 8.0 jsem názor změnil a vysvětlím proč. Otazník
totiž není volitelný v případě properties. Na tomhle PHP zařve:
class Foo
{
private Bar $foo = null;
}
// Fatal error: Default value for property of type Bar may not be null.
// Use the nullable type ?Bar to allow null default value
A dále od PHP 8.0 lze používat promoted
properties, což umožňuje psát takovýto kód:
class Foo
{
public function __construct(
private ?Bar $foo = null,
string $name = null,
) {
// ...
}
}
Zde je vidět nekonzistence. Pokud je v kódu použito ?Bar
(což je nutnost), mělo by o řádek níže následovat ?string.
A pokud v některých případech budu psát otazník, měl bych ho psát
ve všech.
Zůstává otázka, zda není lepší používat místo otazníku přímo
union typ string|null. Pokud bych třeba chtěl zapsat
Stringable|string|null, verze s otazníkem možná
vůbec není.
Aktualizace: vypadá to, že PHP 8.4 bude zápis s otazníkem přímo
vyžadovat.
Ukončení požadavku v PHP se skládá z těchto kroků prováděných
v uvedeném pořadí:
Volání všech funkcí registrovaných pomocí
register_shutdown_function()
Volání všech metod __destruct()
Vyprázdnění všech output bufferů
Ukončení všech rozšíření PHP (např. sessions)
Vypnutí výstupní vrstvy (odeslání HTTP hlaviček, vyčištění output
handlerů atd.)
Zaměříme se podrobněji na krok č. 2, tedy volání destruktorů.
Samozřejmě už v prvním kroku, tedy při volání registrovaných shutdown
funkcí, může dojít k destrukci objektů, např. pokud některá z funkcí
držela poslední referenci na nějaký objekt nebo pokud byla samotná shutdown
funkce objektem.
Volání destruktorů probíhá takto:
PHP se nejprve pokusí zrušit objekty v globální tabulce symbolů.
Poté volá destruktory všech zbývajících objektů.
Pokud je provádění zastaveno např. kvůli exit(),
zbývající destruktory se nevolají.
ad 1) PHP projde globální tabulku symbolů pozpátku, tj. začne od
proměnné, která byla vytvořena jako poslední, a postupuje k proměnné,
která byla vytvořena jako první. Při procházení zruší všechny objekty
s refcount=1. Tato iterace se provádí, dokud takové objekty existují.
V podstatě se tedy dělá to, že a) odstraní všechny nepoužívané
objekty v globální tabulce symbolů b) pokud se objeví nové nepoužívané
objekty, odstraní je také c) a tak dále. Tento způsob destrukce se
používá proto, aby objekty mohly být závislé na jiných objektech
v destruktoru. Obvykle to funguje dobře, pokud objekty v globálním oboru
nemají komplikované (např. kruhové) vzájemné vazby.
Destrukce globální tabulky symbolů se výrazně liší od destrukce
ostatních tabulek symbolů, viz dále. Pro globální tabulku symbolů tedy PHP
používá chytřejší algoritmus, který se snaží respektovat závislosti
objektů.
ad 2) Ostatní objekty se prochází v pořadí podle jejich vytvoření a
zavolá se jejich destruktor. Ano, PHP pouze zavolá __destruct,
ale ve skutečnosti objekt nezruší (a dokonce ani nezmění jeho refcount).
Pokud se tedy na objekt budou odkazovat jiné objekty, bude stále k dispozici
(i když destruktor již byl zavolán). V jistém smyslu budou používat
jakýsi „napůl zničený“ objekt.
ad 3) V případě, že je provádění zastaveno během volání
destruktorů např. kvůli exit(), zbývající destruktory se
nevolají. Místo toho PHP označí objekty za již destruované. Důležitý
důsledek je, že volání destruktorů není jisté. Případy, kdy se tak
stane, jsou spíše vzácné, ale stát se to může.