Wskaźniki inteligentne (Modern C++)

W nowoczesnym programowaniu w języku C++ biblioteka Standardowa zawiera inteligentne wskaźniki, które są używane w celu zapewnienia, że programy są wolne od przecieków pamięci i zasobów i są bezpieczne dla wyjątków.

Zastosowania inteligentnych wskaźników

Inteligentne wskaźniki są definiowane w std przestrzeni nazw w pliku nagłówka <pamięci> . Są one kluczowe dla RAII lub pozyskiwania zasobów to idiom programowania inicjalizacji . Głównym celem tego idiomu jest zapewnienie, że pozyskiwanie zasobów występuje w tym samym czasie, co inicjacja obiektu, tak aby wszystkie zasoby dla tego obiektu były tworzone i gotowe w jednym wierszu kodu. W praktyce główną zasadą RAII jest dawanie na własność dowolnego zasobu z przyznaną stertą — na przykład dynamicznie przydzielonej pamięci lub uchwytów obiektu systemowego — obiektowi z przyznanym stosem, którego destruktor zawiera kod w celu usunięcia lub zwolnienia zasobów, a także związany z nim kod porządkujący.

W większości przypadków, podczas inicjowania surowego wskaźnika lub uchwytu zasobu w celu wskazania rzeczywistego zasobu, należy natychmiast przekazać wskaźnik do inteligentnego wskaźnika. W nowoczesnym C++, surowe wskaźniki są używane tylko w małych blokach kodu o ograniczonym zakresie, pętlach lub funkcjach pomocniczych, gdzie wydajność ma kluczowe znaczenie i nie ma możliwości popełnienia błędu w zakresie własności.

Poniższy przykład porównuje deklarację surowego wskaźnika z deklaracją inteligentnego wskaźnika.

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 pokazano w przykładzie, inteligentny wskaźnik jest szablonem klasy, którą deklarujesz na stosie i inicjujesz przy użyciu surowego wskaźnika, który wskazuje na obiekt z przydzieloną stertą. Po zainicjowaniu inteligentnego wskaźnika, inteligentny wskaźnik jest właścicielem wskaźnika surowego. Oznacza to, że inteligentny wskaźnik jest odpowiedzialny za usunięcie pamięci określonej przez surowy wskaźnik. Destruktor inteligentnego wskaźnika zawiera wywołanie usunięcia, a ponieważ inteligentny wskaźnik jest zadeklarowany na stosie, jego destruktor jest wywołany, kiedy inteligentny wskaźnik wychodzi poza zakres, nawet jeśli później na stosie jest zgłoszony wyjątek.

Uzyskaj dostęp do hermetyzowanego wskaźnika przy użyciu znanych operatorów -> wskaźnika i *, które przeciążenia klasy inteligentnego wskaźnika w celu zwrócenia hermetyzowanego wskaźnika pierwotnego.

Idiom inteligentnego wskaźnika języka C++ przypomina tworzenie obiektów w językach takich, jak C#: utwórz obiekt, a następnie pozwól systemowi zadbać o usunięcie go w odpowiednim czasie. Różnica polega na tym, że w tle nie działa żaden odrębny moduł odśmiecania; pamięć jest zarządzana przez standardowe zasady zakresu C++, tak że środowisko wykonawcze jest szybsze i wydajniejsze.

Ważne

Zawsze twórz inteligentne wskaźniki w osobnym wierszu kodu, nigdy na liście parametrów, tak aby nie wystąpił niewielki wyciek zasobów z powodu pewnych reguł alokacji listy parametrów.

W poniższym przykładzie pokazano, jak unique_ptr inteligentny typ wskaźnika z standardowej biblioteki języka C++ może służyć do hermetyzacji wskaźnika do dużego obiektu.


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.

W przykładzie pokazano niezbędne kroki używania inteligentnych wskaźników.

  1. Zadeklaruj inteligentny wskaźnik jako zmienną automatyczną (lokalną). (Nie używaj new wyrażenia or malloc w samym wskaźniku inteligentnym).

  2. W parametrze typu określ typ wskazywanego zhermetyzowanego wskaźnika.

  3. Przekaż nieprzetworzony wskaźnik do newobiektu -ed w konstruktorze inteligentnego wskaźnika. (Niektóre funkcje narzędziowe lub konstruktory inteligentnego wskaźnika robią to automatycznie.)

  4. Użyj przeciążonych -> operatorów i * , aby uzyskać dostęp do obiektu.

  5. Niech inteligentny wskaźnik usunie obiekt.

Inteligentne wskaźniki są zaprojektowane tak, aby były maksymalnie efektywne, zarówno w zakresie pamięci, jak i wydajności. Na przykład jedynym elementem członkowskim danych w elemencie unique_ptr jest hermetyzowany wskaźnik. Oznacza to, że unique_ptr jest to dokładnie taki sam rozmiar jak ten wskaźnik— cztery bajty lub osiem bajtów. Uzyskiwanie dostępu do hermetyzowanego wskaźnika przy użyciu inteligentnego wskaźnika przeciążonego * i —> operatory nie są znacznie wolniejsze niż uzyskiwanie bezpośredniego dostępu do nieprzetworzonych wskaźników.

Inteligentne wskaźniki mają własne funkcje członkowskie, do których uzyskuje się dostęp przy użyciu notacji "kropka". Na przykład niektóre wskaźniki inteligentne biblioteki standardowej języka C++ mają funkcję składową resetowania, która zwalnia własność wskaźnika. Jest to przydatne, kiedy chcesz zwolnić pamięć zajmowaną przez inteligentny wskaźnik, zanim inteligentny wskaźnik wyjdzie poza zakres, jak pokazano w poniższym przykładzie.

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...

}

Inteligentne wskaźniki umożliwiają zwykle bezpośredni dostęp do własnego surowego wskaźnika. W tym celu inteligentne wskaźniki biblioteki standardowej języka C++ mają funkcję składową get i CComPtr mają składową klasy publicznej p . Zapewniając bezpośredni dostęp do podstawowych wskaźników, możesz skorzystać z inteligentnego wskaźnika do zarządzania pamięcią we własnym kodzie i nadal przekazywać surowy wskaźnik do kodu, który nie obsługuje inteligentnych wskaźników.

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

Rodzaje inteligentnych wskaźników

Poniższa sekcja podsumowuje różne rodzaje inteligentnych wskaźników, które są dostępne w środowisku programowania Windows, i opisuje, kiedy ich używać.

Wskaźniki inteligentne biblioteki standardowej języka C++

Używaj tych inteligentnych wskaźników jako pierwszych, w celu hermetyzacji wskaźników jako zwykłych starych obiektów C++ (Plain Old C++ Objects — POCO).

  • unique_ptr
    Pozwala na dokładnie jednego właściciela podstawowego wskaźnika. Użyj jako domyślnego wyboru dla poco, chyba że wiesz, że na pewno potrzebujesz shared_ptr. Może być przeniesiony do nowego właściciela, ale nie kopiowany lub udostępniony. auto_ptrZastępuje element , który jest przestarzały. Porównaj z boost::scoped_ptr. unique_ptr jest mały i wydajny; rozmiar jest jednym wskaźnikiem i obsługuje odwołania rvalue do szybkiego wstawiania i pobierania z kolekcji biblioteki standardowej języka C++. Plik nagłówka: <memory>. Aby uzyskać więcej informacji, zobacz How to: Create and Use unique_ptr Instances and unique_ptr Class (Jak tworzyć i używać wystąpień unique_ptr i klas unique_ptr).

  • shared_ptr
    Inteligentny wskaźnik zliczonych odwołań. Użyj, jeżeli chcesz przypisać jeden surowy wskaźnik wielu właścicielom, na przykład, kiedy zwracasz kopię wskaźnika z kontenera, ale chcesz zatrzymać oryginał. Nieprzetworzona wskaźnik nie zostanie usunięta, dopóki wszyscy shared_ptr właściciele nie wyjdą z zakresu lub nie zrezygnowali z własności. Rozmiar to dwa wskaźniki; jeden dla obiektu i jeden dla współdzielonego bloku kontroli, który zawiera licznik odwołań. Plik nagłówka: <memory>. Aby uzyskać więcej informacji, zobacz How to: Create and Use shared_ptr Instances and shared_ptr Class (Instrukcje: tworzenie i używanie wystąpień shared_ptr oraz klasa shared_ptr).

  • weak_ptr
    Inteligentny wskaźnik specjalnego przypadku do użycia w połączeniu z shared_ptr. Obiekt A weak_ptr zapewnia dostęp do obiektu należącego do co najmniej jednego shared_ptr wystąpienia, ale nie uczestniczy w zliczaniu odwołań. Używaj, jeżeli chcesz obserwować obiekt, ale nie wymagasz, aby pozostał aktywny. Wymagane w niektórych przypadkach, aby przerwać odwołania cykliczne między shared_ptr wystąpieniami. Plik nagłówka: <memory>. Aby uzyskać więcej informacji, zobacz How to: Create and Use weak_ptr Instances and weak_ptr Class (Jak tworzyć i używać wystąpień weak_ptr i klas weak_ptr).

Inteligentne wskaźniki dla obiektów COM (klasyczne programowanie systemu Windows)

Kiedy pracujesz z obiektami COM, zawiń wskaźniki interfejsu w odpowiedni typ inteligentnego wskaźnika. Active Template Library (ATL) definiuje kilka inteligentnych wskaźników do różnych celów. Można również użyć typu inteligentnego _com_ptr_t wskaźnika, który kompilator używa podczas tworzenia klas otoki na podstawie plików .tlb. To najlepszy wybór, jeśli nie chcesz dołączyć plików nagłówkowych ATL.

Klasa CComPtr
Użyj tego, jeżeli nie możesz użyć ATL. Wykonuje zliczanie odwołań przy użyciu AddRef metod i .Release Aby uzyskać więcej informacji, zobacz How to: Create and Use CComPtr and CComQIPtr Instances (Instrukcje: tworzenie wystąpień CComPtr i CComQIPtr).

Klasa CComQIPtr
CComPtr Przypomina, ale także zapewnia uproszczoną składnię wywoływania QueryInterface obiektów COM. Aby uzyskać więcej informacji, zobacz How to: Create and Use CComPtr and CComQIPtr Instances (Instrukcje: tworzenie wystąpień CComPtr i CComQIPtr).

Klasa CComHeapPtr
Inteligentny wskaźnik do obiektów używanych CoTaskMemFree do zwalniania pamięci.

Klasa CComGITPtr
Inteligentny wskaźnik dla interfejsów, które są uzyskiwane z tabeli interfejsu globalnego (GIT).

_com_ptr_t, klasa
CComQIPtr Przypomina funkcje, ale nie zależy od nagłówków ATL.

Inteligentne wskaźniki ATL dla obiektów POCO

Oprócz inteligentnych wskaźników dla obiektów COM, ATL definiuje również inteligentne wskaźniki i kolekcje inteligentnych wskaźników dla zwykłych starych obiektów C++ (POCO). W klasycznym programowaniu systemu Windows te typy są przydatnymi alternatywami dla kolekcji biblioteki standardowej języka C++, zwłaszcza gdy przenośność kodu nie jest wymagana lub gdy nie chcesz mieszać modeli programowania biblioteki standardowej języka C++ i ATL.

Klasa CAutoPtr
Inteligentny wskaźnik, który wymusza unikatowe własności poprzez przeniesienie własności na kopię. Porównywalne z przestarzałą std::auto_ptr klasą.

Klasa CHeapPtr
Inteligentny wskaźnik dla obiektów przydzielonych przy użyciu funkcji malloc języka C.

Klasa CAutoVectorPtr
Inteligentny wskaźnik dla tablic przydzielonych przy użyciu polecenia new[].

Klasa CAutoPtrArray
Klasa, która hermetyzuje tablicę CAutoPtr elementów.

Klasa CAutoPtrList
Klasa, która hermetyzuje metody manipulowania listą węzłów CAutoPtr .

Zobacz też

Wskaźniki
Dokumentacja języka C++
Standardowa biblioteka C++