Chytré ukazatele (moderní verze jazyka C++)

V moderním programování v jazyce C++ standardní knihovna zahrnuje chytré ukazatele, které pomáhají zajistit, aby v programech nedocházelo k únikům paměti a prostředků a aby byly bezpečné i při výskytu výjimek.

Použití inteligentních ukazatelů

Inteligentní ukazatele jsou definovány v oboru názvů std v hlavičkovém souboru <memory>. Jsou klíčové pro programátorský idiom získání prostředků je inicializace(RAII). Hlavním cílem tohoto idiomu je zajistit, aby získání prostředků probíhalo ve stejnou dobu, kdy je objekt inicializován. Všechny prostředky pro objekt jsou vytvořeny a připraveny v jediném řádku kódu.

Z praktického hlediska je hlavním principem RAII svěřit vlastnictví jakéhokoli zdroje alokovaného na haldě objektu alokovanému na zásobníku, jehož destruktor obsahuje kód pro odstranění nebo uvolnění daného zdroje a také veškerý související vyčišťovací kód. Tyto objekty zahrnují dynamicky přidělenou paměť nebo systémové popisovače objektů.

Ve většině případů platí, že když inicializujete surový ukazatel nebo popisovač prostředku tak, aby odkazoval na skutečný prostředek, předejte tento ukazatel okamžitě inteligentnímu ukazateli. V moderním jazyce C++ se surové ukazatele používají pouze v malých blocích kódu s omezenou platností, ve smyčkách nebo v pomocných funkcích, kde je výkon zásadní a nemůže dojít k nejasnostem ohledně vlastnictví.

Následující příklad porovnává deklaraci nezpracovaného a inteligentního ukazatele.

void UseRawPointer()
{
    // Using a raw pointer -- not recommended.
    Song* pSong = new Song(L"Nothing on You", L"Bruno Mars"); 

    // Use pSong...

    // Don't forget to delete!
    delete pSong;   
}

void UseSmartPointer()
{
    // Declare a smart pointer on stack and pass it the raw pointer.
    unique_ptr<Song> song2(new Song(L"Nothing on You", L"Bruno Mars"));

    // Use song2...
    wstring s = song2->duration_;
    //...

} // song2 is deleted automatically here.

Jak je vidět v příkladu, inteligentní ukazatel je šablona třídy, kterou deklarujete na zásobníku a inicializujete pomocí surového ukazatele, který ukazuje na objekt alokovaný na haldě. Po inicializaci inteligentní ukazatel vlastní nezpracovaný ukazatel. Tento přístup znamená, že inteligentní ukazatel je zodpovědný za uvolnění paměti, na kterou ukazuje surový ukazatel.

Destruktor inteligentního ukazatele obsahuje volání delete. Vzhledem k tomu, že inteligentní ukazatel je deklarován v zásobníku, je jeho destruktor vyvolán, když inteligentní ukazatel přejde mimo rozsah. Vyvolá se i v případě, že je výjimka vyvolána někde dále v zásobníku.

Přístup k zapouzdřenému ukazateli pomocí známých operátorů ukazatele: -> a *. Třída inteligentního ukazatele přetěžuje tyto operátory tak, aby vracely zapouzdřený surový ukazatel.

Idiom inteligentního ukazatele jazyka C++ se podobá vytváření objektů v jazycích, jako je C#. Objekt vytvoříte a pak necháte systém, aby se o jeho odstranění postaral ve správný čas. Rozdíl je v tom, že na pozadí neběží žádný samostatný garbage collector. Paměť se spravuje prostřednictvím standardních pravidel oborů jazyka C++, aby prostředí runtime bylo rychlejší a efektivnější.

Důležité

Vždy vytvářejte inteligentní ukazatele na samostatném řádku kódu, nikdy v seznamu parametrů. Tento přístup brání drobnému úniku prostředků kvůli určitým pravidlům přidělování seznamu parametrů.

Následující příklad ukazuje, jak lze unique_ptr k zapouzdření ukazatele na velký objekt použít inteligentní typ ukazatele ze standardní knihovny jazyka C++.

class LargeObject
{
public:
    void DoSomething(){}
};

void ProcessLargeObject(const LargeObject& lo){}
void SmartPointerDemo()
{    
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Pass a reference to a method.
    ProcessLargeObject(*pLarge);

} //pLarge is deleted automatically when function block goes out of scope.

Příklad demonstruje následující základní kroky pro použití inteligentních ukazatelů.

  1. Deklarujte inteligentní ukazatel jako automatickou (místní) proměnnou. Nepoužívejte new ani malloc výraz na samotném inteligentním ukazateli.

  2. V parametru typu zadejte typ, na který zapouzdřený ukazatel odkazuje.

  3. Předejte do konstruktoru inteligentního ukazatele surový ukazatel na objekt opatřený new. Některé pomocné funkce nebo konstruktory inteligentních ukazatelů tuto akci dělají za vás.

  4. Pro přístup k objektu použijte přetížené operátory -> a *.

  5. Umožněte inteligentnímu ukazateli odstranit objekt.

Inteligentní ukazatele jsou navrženy tak, aby byly co nejúčinnější z hlediska paměti i výkonu. Jediným datovým členem unique_ptr je například zapouzdřený ukazatel. Tato skutečnost znamená, že unique_ptr je přesně stejná velikost jako tento ukazatel, a to buď čtyři bajty, nebo osm bajtů. Přístup k zapouzdřenému ukazateli pomocí přetížených operátorů * a -> inteligentního ukazatele není výrazně pomalejší než přímý přístup k surovým ukazatelům.

Inteligentní ukazatele mají své vlastní členské funkce, ke kterým se přistupuje pomocí zápisu tečky. Například některé inteligentní ukazatele standardní knihovny C++ mají členskou funkci reset, která uvolňuje vlastnictví ukazatele. Tento aspekt je užitečný, pokud chcete uvolnit paměť vlastněnou inteligentním ukazatelem před tím, než inteligentní ukazatel přejde mimo rozsah, jak je znázorněno v následujícím příkladu.

void SmartPointerDemo2()
{
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Free the memory before we exit function block.
    pLarge.reset();

    // Do some other work...

}

Inteligentní ukazatele obvykle umožňují přímý přístup k příslušnému nezpracovanému ukazateli. Inteligentní ukazatele standardní knihovny C++ mají pro tento účel členskou funkci get. CComPtr má člena veřejné p třídy. Poskytnutím přímého přístupu k podkladovému ukazateli můžete inteligentní ukazatel použít ke správě paměti ve vlastním kódu a stále předat nezpracovaný ukazatel kódu, který nepodporuje inteligentní ukazatele.

void SmartPointerDemo4()
{
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Pass raw pointer to a legacy API
    LegacyLargeObjectFunction(pLarge.get());    
}

Druhy inteligentních ukazatelů

V následující části jsou shrnuty různé druhy inteligentních ukazatelů, které jsou k dispozici v programovacím prostředí Windows, včetně popisu, kdy je vhodné je použít.

Inteligentní ukazatele standardní knihovny C++

Tyto inteligentní ukazatele používejte jako první volbu pro zapouzdření ukazatelů na běžné objekty C++ (POCO).

  • unique_ptr

    Umožňují, aby podkladový ukazatel měl právě jednoho vlastníka. Toto použijte jako výchozí volbu pro POCO, pokud s jistotou nevíte, že potřebujete shared_ptr. Lze je přesunout na nového vlastníka, nikoli však kopírovat nebo sdílet. Nahrazuje auto_ptr, která je zastaralá. Porovnat s boost::scoped_ptr.

    unique_ptr je malá a účinná. Velikost je jeden ukazatel a podporuje odkazy rvalue pro rychlé vkládání a načítání z kolekcí standardní knihovny C++. Hlavičkový soubor: <memory>. Další informace najdete v tématu Postupy: Vytváření a používání instancí unique_ptr a třídy unique_ptr.

  • shared_ptr

    Inteligentní ukazatel s počítáním referencí Použijte, když chcete přiřadit jeden nezpracovaný ukazatel více vlastníkům. Můžete například vrátit kopii ukazatele z kontejneru, ale zachovat i původní. Surový ukazatel se neodstraní, dokud všichni vlastníci shared_ptr nezaniknou nebo se jinak nevzdají vlastnictví.

    Velikost odpovídá dvěma ukazatelům: jednomu na objekt a jednomu na sdílený řídicí blok, který obsahuje počet referencí. Hlavičkový soubor: <memory>. Další informace naleznete v tématu Postupy: Vytvoření a použití instancí shared_ptr a třídy shared_ptr.

  • weak_ptr

    Inteligentní ukazatel pro speciální případ pro použití s shared_ptr. weak_ptr umožňuje přístup k objektu, který vlastní jedna či více instancí shared_ptr, ale nepodílí se na počítání referencí. Použijte, když chcete pozorovat objekt, ale nevyžadujete, aby zůstal naživu. Vyžaduje se v některých případech k přerušení cyklických odkazů mezi shared_ptr instancemi.

    Hlavičkový soubor: <memory>. Další informace naleznete v tématu Postupy: Vytvoření a použití instancí weak_ptr a třídy weak_ptr.

Inteligentní ukazatele pro objekty MODELU COM (klasické programování systému Windows)

Při práci s objekty COM zabalte ukazatele rozhraní do příslušného typu inteligentního ukazatele. Knihovna Active Template Library (ATL) definuje několik inteligentních ukazatelů pro různé účely. Můžete také použít typ inteligentního _com_ptr_t ukazatele, který kompilátor používá při vytváření tříd obálky ze souborů .tlb. Nejlepší volbou je, když nechcete zahrnout soubory hlaviček ATL.

Inteligentní ukazatele ATL pro objekty POCO

Kromě inteligentních ukazatelů pro objekty COM definuje ATL také inteligentní ukazatele a kolekce inteligentních ukazatelů pro prosté staré objekty C++ (POCO). V klasickém programování Windows jsou tyto typy užitečné alternativy ke kolekcím standardní knihovny C++, zejména pokud není vyžadována přenositelnost kódu nebo pokud nechcete kombinovat programovací modely standardní knihovny C++ a knihovny ATL.

  • Třída CAutoPtr

    Inteligentní ukazatel, který vynucuje jedinečné vlastnictví přenosem vlastnictví při kopírování. Srovnatelné s zastaralou std::auto_ptr třídou.

  • Třída CHeapPtr

    Inteligentní ukazatel pro objekty, které jsou přiděleny pomocí funkce C malloc .

  • Třída CAutoVectorPtr

    Inteligentní ukazatel pro pole, která jsou přidělena pomocí new[].

  • Třída CAutoPtrArray

    Třída, která zapouzdřuje pole prvků CAutoPtr.

  • CAutoPtrList – třída

    Třída, která zapouzdřuje metody pro manipulaci se seznamem CAutoPtr uzlů.

Viz také