Smart pointer

Smart pointer je v matematické informatice abstraktní datový typ, který poskytuje funkčnost ukazatele a poskytuje další vlastnosti, např. automatickou správu paměti nebo kontrolu mezí pole, které by měly omezit programátorské chyby způsobené chybným použitím ukazatelů bez snížení efektivity. Smart pointery obvykle sledují paměť, na kterou ukazují, ale mohou sloužit i pro správu jiných prostředků, např. síťových spojení nebo deskriptorů souborů. Smart pointery byly poprvé zpopularizován v programovacím jazyce C++ v první polovině 90. let 20. století jako odpověď na kritiku nedostatku automatické správy paměti (garbage collection) v jazyce C++.[1][2]

Chybné použití ukazatelů je častým zdrojem chyb. Smart pointery zabraňují většině situací úniku paměti zajištěním automatického vrácení paměti. Obecněji řečeno, podporují automatické rušení objektů: objekt řízený smart pointerem je automaticky uklizen (finalizován a pak uvolněn), když je zničen poslední (nebo jediný) vlastník objektu, například protože vlastník je lokální proměnná, a program opustí její rozsah platnosti. Smart pointery také odstraňují dangling ukazatele tím, že odloží zničení objektu až do okamžiku, kdy se na něj neodkazuje žádný ukazatel.

Pokud jazyk podporuje automatické uvolňování paměti (jako např. Java nebo C#), pak není třeba používat smart pointery pro uvolňování paměti a bezpečnostní aspekty správy paměti, ale jsou užitečné pro jiné účely, např. pro správu cachování datových struktur nebo správu jiných prostředků např. manipulátorů souborů nebo síťových soketů.

Existuje několik druhů smart pointerů. Některé využívají počítání odkazů, jiné předávání vlastnictví objektu mezi ukazateli.

Historie

Přestože koncept smart pointerů, zvláště počítání odkazů,[3] zpopularizoval jazyk C++, bezprostřední předchůdce jednoho z jazyků, který inspiroval návrh jazyka C++, měl počítání referencí zabudované přímo v jazyce. C++ byl částečně inspirován jazykem Simula67,[4] jehož předchůdcem byla Simula I. Nakolik má element v Simule I za obdobu ukazatel v C++ bez hodnoty null, natolik má proces Simuly I s prázdným příkazem jako výkonnou částí za obdobu struct v C++ (který samotný je obdobou záznamu C. A. R. Hoareho z jeho práci z 60. let 20. století), Simula I měla počítání referencí na elements (tj. ukazatelové výrazy, které obsahují indirekci) na procesy (tj. záznamy) přinejmenším od září 1965, jak je uvedeno v následujících citovaných odstavcích.[5]

Na procesy se lze odkazovat jednotlivě. Fyzicky je odkaz na proces ukazatel na oblast paměti obsahující lokální data procesu a některé další informace, které definují jeho okamžitý stav provádění. Z důvodů uvedených v Části 2.2 jsou však odkazy na procesy vždy nepřímé, pomocí položek nazývaných elements. Formálně je odkaz na proces hodnotou výrazu typu element.

Hodnoty elementu lze ukládat a načítat především pomocí přiřazení a referencí proměnné typu element proměnné.
Jazyk obsahuje mechanismus, který zpřístupňuje atributy procesu zvenčí, tj. z jiných procesů. Tento mechanismus se nazývá vzdálený přístup (anglicky remote access). Proces je tedy datová struktura, na kterou je možné se odkazovat.

Je třeba upozornit na podobnost mezi procesem, jehož kódem je prázdný příkaz, a konceptem záznamu, který nedávno navrhl C. A. R. Hoare a N. Wirth

Protože C++ si z jazyka Simula vypůjčil přístup k přidělování paměti pomocí klíčového slova new při alokování procesu/záznamu pro získání nového elementu pro tento proces/záznam, není překvapivé, že C++ nakonec také znovuvzkřísilo mechanismus smart ukazatelů s počítáním odkazů z jazyka Simula uvnitř elementu.

Vlastnosti

V C++ je smart pointer implementován jako šablona třídy, která pomocí přetěžování operátorů napodobuje chování běžných (syrových) ukazatelů, (tj. dereferenci a přiřazení), ale navíc poskytuje další vlastnosti pro správu paměti.

Smart pointery mohou usnadňovat záměrné programování tím, že v popisu typu umožňují vyjádřit, jak bude spravována paměť, na kterou se odkazuje ukazatel. Pokud například C++ funkce vrací ukazatel, není možné zjistit, zda má volající uvolnit odkazovanou paměť, když tato data již nepotřebuje.

nějaký typ* nejednoznačná_funkce();  // Jak se má nakládat s pamětí, na níž ukazuje výsledek?

Pro odstranění nejednoznačnosti se tradičně používaly pojmenovávací konvence,[6] což je pracný, k chybám náchylný, přístup. C++11 představil cestu, jak v takovém případě zajistit bezchybnou správu paměti deklarací, že funkce vrací unique_ptr,

std::unique_ptr<nějaký typ> jednoznačná_funkce();

Použití unique_ptr pro návratový typ funkce, jednoznačně vyjadřuje, že volající přebírá vlastnictví výsledku, a běhová podpora C++ zajistí automatické uvolnění paměti. Do verze C++11 bylo možné místo unique_ptr používat auto_ptr, který nadále není doporučovaný.

Vytváření nových objektů

Pro usnadnění vytváření objektů

std::shared_ptr<nějaký typ>

C++11 zavedl:

auto s = std::make_shared<nějaký typ>(parametry konstruktoru);

a podobně pro

std::unique_ptr<nějaký typ>

lze od C++14 používat:

auto u = std::make_unique<nějaký typ>(parametry konstruktoru);

Použití těchto konstrukcí místo klíčového slova new je upřednostňované téměř ve všech případech.[7]

unique_ptr

V C++11 byl zaveden std::unique_ptr, definovaný v hlavičkovém souboru <memory>.[8]

unique_ptr je kontejner pro syrový ukazatel, který je vlastnictvím unique_ptr. unique_ptr explicitně brání kopírování ukazatele, který je v něm obsažen (což by provedlo normální přiřazení); pro přenos vlastnictví zapouzdřeného ukazatele však lze použít funkci std::move, která ukazatel přesune do jiného unique_ptr. Objekty unique_ptr není možné kopírovat, protože jejich kopírovací konstruktor a operátory přiřazení jsou explicitně smazané.

std::unique_ptr<int> p1(new int(5));
std::unique_ptr<int> p2 = p1;  // Způsobí chybu při překladu
std::unique_ptr<int> p3 = std::move(p1);  // Předá vlastnictví. p3 nyní vlastní paměť a p1 je nullptr.

p3.reset();  // Uvolní paměť
p1.reset();  // Neudělá nic

std::auto_ptr je od verze C++11 nedoporučovaný a od C++17 je úplně odstraněný. Kopírovací konstruktor a operátory přiřazení objektu auto_ptr ve skutečnosti nekopírují zapouzdřený ukazatel, ale přenesou jej a původní objekt auto_ptr vyprázdní. To byl jeden ze způsobů, jak implementovat striktní vlastnictví, aby v každém okamžiku mohl ukazatel vlastnit pouze jeden objekt auto_ptr. To znamená, že auto_ptr by se neměl používat tam, kde je potřebná kopírovací sémantika.[9][zdroj?] Protože auto_ptr byl zaveden s kopírovací sémantikou, nebylo možné jej přeměnit na ukazatel pouze pro přesun bez porušení zpětné kompatibility s existujícím kódem.

shared_ptr a weak_ptr

V C++11 bylo také zavedeno std::shared_ptr a std::weak_ptr definované v hlavičkovém souboru <memory>[8] a std::make_shared (std::make_unique bylo zavedeno v C++14) pro bezpečnou alokaci dynamické paměti podle paradigmatu RAII.[10]

shared_ptr je kontejner pro syrový ukazatel. Pomocí počítání odkazů si pamatuje počet vlastníků zapouzdřeného ukazatele ve spolupráci se všemi kopiemi objektu shared_ptr. Objekt referencovaný zapouzdřeným syrovým ukazatelem bude uvolněn právě v okamžiku, kdy dojde k zániku všech kopií shared_ptr.

std::shared_ptr<int> p0(new int(5));  // Validní, alokuje jednu hodnotu typu int a inicializuje ji hodnotou 5.
std::shared_ptr<int[]> p1(new int[5]);  // Validní, alokuje 5 hodnot typu int.
std::shared_ptr<int[]> p2 = p1;  // Oba ukazatelé vlastní entitu, na kterou se odkazují.

p1.reset();  // Referencovaný objekt stále existuje díky p2.
p2.reset();  // Referencovaný objekt je zrušen, protože na něj již nic neukazuje.

weak_ptr je kontejner pro syrový ukazatel. Vytváří se kopírováním objektu shared_ptr. Existence nebo zničení weak_ptr kopií objektu shared_ptr nemá žádný vliv na shared_ptr ani na jeho další kopie. Jakmile však jsou všechny kopie objektu shared_ptr zničeny, všechny kopie weak_ptr se vyprázdní.

std::shared_ptr<int> p1 = std::make_shared<int>(5);
std::weak_ptr<int> wp1 {p1};  // p1 vlastní referencovaný objekt.

{
  std::shared_ptr<int> p2 = wp1.lock();  // Nyní p1 a p2 vlastní entitu, na kterou se odkazují.
  // p2 je inicializován z weak pointeru, takže před jeho použitím je třeba kontrolovat, zda
  // se paměť stále používá!
  if (p2) {
    Pracuj_s(p2);
  }
}
// p2 je zrušen. Paměť vlastní p1.

p1.reset();  // Uvolní paměť

std::shared_ptr<int> p3 = wp1.lock(); 
// Paměť je uvolněna, takže dostaneme prázdný shared_ptr.
if (p3) {  // kód se neprovede
  akce_která_potřebuje_živý_ukazatel(p3);
}

Počítání odkazů používané v shared_ptr má problém s cyklickými referencemi. Cyklický řetěz z shared_ptr lze přerušit tak, že jedna z referencí je weak_ptr.

K různým shared_ptr a weak_ptr objektům, které ukazují na stejný objekt, může bezpečně současně přistupovat několik vláken.[11]

Referencovaný objekt musí být odděleně chráněn pro zajištění vláknové bezpečnosti.

shared_ptr a weak_ptr jsou založeny na verzi používané knihovnami Boost.[zdroj?] C++ Technická Zpráva 1 (TR1) je poprvé představila ve standardním C++, jako obecné nástroje, a C++11 k tomuto základu přidává další funkce.

Další druhy smart pointerů

Existují další druhy smart pointerů (které nejsou ve standardním C++) implementovaných v oblíbených C++ knihovnách nebo v STL, např. hazard ukazatel[12] a intrusive ukazatel.[13] [14]

Odkazy

Reference

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

  1. KLINE, Marshall. C++ FAQs Lite's sections on reference-counted smart pointers and copy-on-write reference semantics in the freestore management FAQs [online]. cis.usouthal.edu, září 1997 [cit. 2018-04-06]. [16.20 Dostupné online. 
  2. COLVIN, Gregory, 1994. proposal to standardize counted_ptr in the C++ standard library [online]. open-std.org, 1994 [cit. 2018-04-06]. Dostupné online. 
  3. KLABNIK, Steve; NICHOLS, Carol, 2023. The Rust Programming Language. 2. vyd. [s.l.]: No Starch Press, Inc.. ISBN 978-1-7185-0310-6. Kapitola 15. Smart Pointers, s. 315–351.  (xxix+1+527+3 pages)
  4. STROUSTRUP, Bjarne. A history of C++: 1979–1991 [online]. [cit. 2018-04-06]. Dostupné online. 
  5. DAHL, Ole-Johan; NYGAARD, Kristen. SIMULA—An ALGOL-based simulation language [online]. folk.uio.no, září 1966 [cit. 2018-04-06]. Dostupné online. 
  6. Taligent's Guide to Designing Programs, section Use special names for copy, create, and adopt routines [online]. Dostupné online. 
  7. SUTTER, Herb. Trip Report: ISO C++ Spring 2013 Meeting [online]. isocpp.org, 2013-04-20 [cit. 2013-06-14]. Dostupné online. 
  8. a b ISO 14882:2011 20.7.1
  9. CERT C++ Secure Coding Standard
  10. ISO 14882:2014 20.7.1
  11. boost::shared_ptr thread safety [online]. Dostupné online.  (NB. Does not formally cover std::shared_ptr, but is believed to have the same threading limitations.)
  12. folly/Hazptr.h at main · facebook/folly [online]. github.com. Dostupné online. 
  13. Boost.SmartPtr: The Smart Pointer Library - 1.81.0 [online]. boost.org. Dostupné online. 
  14. EASTL/intrusive_ptr.h at master · electronicarts/EASTL [online]. github.com. Dostupné online. 

Literatura

Související články

Externí odkazy

Média použitá na této stránce

Under construction icon-orange.svg
Autor:

odvozené dílo Pedroca cerebral

Derivative work: Bazi, Licence: LGPL
Under construction icon