Přeskočit na obsah

Smart pointer

Z Wikipedie, otevřené encyklopedie

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]

Smart pointery zabraňují chybnému použití ukazatelů, které je častým zdrojem chyb – např. únikům paměti lze zabránit automatickým vracením paměti. Obecněji 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í problém s dangling ukazateli odložením ruš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.

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

[editovat | editovat zdroj]

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é a náchylné k chybám. Lepší možností, jak zajistit bezchybnou správu paměti, je použití unique_ptr zavedené v C++11, které jednoznačně vyjadřuje, že vlastnictví paměti výsledku přebírá volající, a automatické uvolnění paměti zajistí běhová podpora C++:

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

Před verzí C++11 se místo unique_ptr používalo nyní nedoporučované auto_ptr.

Vytváření nových objektů

[editovat | editovat zdroj]

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);

Tyto konstrukce by se měly používat téměř ve všech případech místo new.[7]

unique_ptr

[editovat | editovat zdroj]

C++11 zavádí std::unique_ptr definovaný v hlavičkovém souboru <memory>[8] jako kontejner, který vlastní syrový ukazatel a zrušením kopírovacího konstruktor a operátorů přiřazení explicitně brání jeho kopírování (které 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:[9]

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

Od verze C++11 je std::auto_ptr nedoporučovaný a ve verzi C++17 byl 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.[10] 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

[editovat | editovat zdroj]

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 (osvojení prostředku je inicializace).[11]

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.[12]

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

shared_ptr a weak_ptr vycházejí z verze 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ů

[editovat | editovat zdroj]

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[13] a intrusive ukazatel.[14] [15]

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. BALLMAN, Aaron; SCHIELA, Robert, 2024. SEI CERT C++ Coding Standard; Rules for Developing Safe, Reliable, and Secure Systems in C++ [online]. Carnegie Mellon University, 2024. Dostupné online. 
  10. BALLMAN, Aaron, 2016. SEI CERT C++ Coding Standard; Rules for Developing Safe, Reliable, and Secure Systems in C++ [online]. Carnegie Mellon University, 2016. Dostupné online. 
  11. ISO 14882:2014 20.7.1
  12. boost::shared_ptr thread safety [online]. Dostupné online.  (Poznámnka: formálně nepokrývá std::shared_ptr, ale předpokládá se, že má stejná omezení v multithreadovém prostředí.)
  13. folly/Hazptr.h at main · facebook/folly [online]. github.com. Dostupné online. 
  14. Boost.SmartPtr: The Smart Pointer Library - 1.81.0 [online]. boost.org. Dostupné online. 
  15. EASTL/intrusive_ptr.h at master · electronicarts/EASTL [online]. github.com. Dostupné online. 

Literatura

[editovat | editovat zdroj]

Související články

[editovat | editovat zdroj]

Externí odkazy

[editovat | editovat zdroj]