Udostępnij za pośrednictwem


Wskaźniki inteligentne (Modern C++)

W nowoczesnym programowaniu C++, standardowa biblioteka zawiera inteligentne wskaźniki, których używa się, aby zapewnić, że programy są wolne od wycieków pamięci i zasobów i że są bezpieczne pod względem wyjątków.

Zastosowania inteligentnych wskaźników

Inteligentne wskaźniki są zdefiniowane w std przestrzeni nazw w pliku nagłówkowym <memory>.Są niezbędne dla idiomu programowania RAII czyli Resource Acquisition Is Initialization (Pozyskiwanie zasobów jest inicjalizacją).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 zhermetyzowanego wskaźnika za pomocą znanych operatorów wskaźnika -> i *, która klasa inteligentnego wskaźnika przeciąża, aby zwrócić zhermetyzowany surowy wskaźnik.

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żna uwagaWaż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 typ inteligentnego wskaźnika unique_ptr standardowej biblioteki szablonów może być użyty 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 wyrażenia new ani malloc inteligentnego wskaźnika jako takiego.)

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

  3. Przekaż surowy wskaźnik do obiektu new 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, jedyny element członkowski w unique_ptr to zhermetyzowany wskaźnik.Oznacza to, że unique_ptr ma dokładnie taki sam rozmiar jak ten wskaźnik, cztery bity lub osiem bitów.Dostęp do zhermetyzowanego wskaźnika za pomocą przeciążonych operatorów * i -> inteligentnego wskaźnika nie jest znacznie wolniejszy niż bezpośredni dostęp do surowych wskaźników.

Inteligentne wskaźniki mają swoje własne funkcje członkowskie, które są dostępne przy użyciu notacji „kropkowej”.Na przykład, niektóre inteligentne wskaźniki STL mają funkcję resetowania elementu członkowskiego, która uwalnia 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.Inteligentne wskaźniki STL mają w tym celu funkcję elementu członkowskiego get, a CComPtr ma element członkowski p klasy publicznej.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ć.

  • Inteligentne wskaźniki standardowej biblioteki 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 na pewno, że potrzebujesz shared_ptr.Może być przeniesiony do nowego właściciela, ale nie kopiowany lub udostępniony.Zastępuje auto_ptr, który jest zaniechany.Porównaj z boost::scoped_ptr.unique_ptr jest mały i wydajny, rozmiar to jeden wskaźnik, i obsługuje odwołania rvalue dla ostatniego wstawienia i wydobycia z kolekcji STL.Plik nagłówkowy: <memory>.Aby uzyskać więcej informacji, zobacz Porady: tworzenie wystąpień unique_ptr i korzystanie z nich i unique_ptr — Klasa.

    • 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ł.Surowy wskaźnik nie jest usuwany do czasu, aż wszyscy właściciele shared_ptr wyjdą poza zakres lub w inny sposób zrezygnują z posiadania.Rozmiar to dwa wskaźniki; jeden dla obiektu i jeden dla współdzielonego bloku kontroli, który zawiera licznik odwołań.Plik nagłówkowy: <memory>.Aby uzyskać więcej informacji, zobacz Porady: tworzenie wystąpień shared_ptr i korzystanie z nich i shared_ptr — Klasa.

    • weak_ptr
      Szczególny inteligentny wskaźnik używany w połączeniu z shared_ptr.weak_ptr zapewnia dostęp do obiektu, który jest własnością jednego lub więcej wystąpień shared_ptr, 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 złamać odwołania cykliczne między wystąpieniami shared_ptr.Plik nagłówkowy: <memory>.Aby uzyskać więcej informacji, zobacz Porady: tworzenie wystąpień weak_ptr i korzystanie z nich i weak_ptr — Klasa.

  • Inteligentne wskaźniki dla obiektów COM (klasyczne programowanie 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 wskaźnika _com_ptr_t, który jest wykorzystywany przez kompilator przy tworzeniu klas otoki z plików .tlb.To najlepszy wybór, jeśli nie chcesz dołączyć plików nagłówkowych ATL.

  • Inteligentne wskaźniki ATL dla obiektów POCO
    Oprócz inteligentnych wskaźników dla obiektów COM, ATL także definiuje inteligentne wskaźniki i kolekcje inteligentnych wskaźników dla zwykłych starych obiektów C++.W klasycznym programowaniu Windows, te typy są użyteczną alternatywą dla kolekcji STL, w szczególności, gdy przenoszenie kodu nie jest wymagane lub gdy użytkownik nie chce mieszać modeli programowania STL i ATL.

    • Klasa CAutoPtr
      Inteligentny wskaźnik, który wymusza unikatowe własności poprzez przeniesienie własności na kopię.Porównywalne do zaniechanej klasy std::auto_ptr.

    • Klasa CHeapPtr
      Inteligentny wskaźnik dla obiektów, które są przydzielane przy użyciu funkcji C malloc.

    • Klasa CAutoVectorPtr
      Inteligentny wskaźnik dla tablic, które są przydzielane przy użyciu new[].

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

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

Zobacz też

Inne zasoby

Zapraszamy ponownie do języka C++ (Modern C++)

Materiały referencyjne dotyczące języka C++

Odwołanie do standardowej biblioteki C++

Overview: Memory Management in C++