Интеллектуальные указатели (современный C++)

В современном программировании C++ стандартная библиотека включает смарт-указатели, которые используются для обеспечения того, чтобы программы были свободны от утечки памяти и ресурсов и являются исключениями.

Использование интеллектуальных указателей

Интеллектуальные указатели определяются в std пространстве имен в файле заголовка <памяти> . Они имеют решающее значение для идиомы программирования на основе идиомы II или приобретения ресурсов. Главная задача этой идиомы — обеспечить, чтобы одновременно с получением ресурса производилась инициализация объекта, чтобы все ресурсы для объекта создавались и подготавливались в одной строке кода. На практике основным принципом RAII является предоставление владения любым ресурсом в куче (например, динамически выделенной памятью или дескрипторами системных объектов) объекту, выделенному стеком, деструктор которого содержит код для удаления или освобождения ресурса, а также весь связанный код очистки.

В большинстве случаев при инициализации необработанного указателя или дескриптора ресурса для указания на фактический ресурс следует сразу же передать указатель в интеллектуальный указатель. В современном C++ необработанные указатели используются только в небольших блоках кода с ограниченной областью, циклах или вспомогательных функциях, когда важна производительность и вероятность проблем с владением низкая.

В следующем примере сравниваются объявления необработанного и интеллектуального указателей.

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.

Как показано в примере, интеллектуальный указатель — это шаблон класса, который объявляется в стеке и инициализируется с помощью необработанного указателя, указывающего на размещенный в куче объект. После инициализации интеллектуальный указатель становится владельцем необработанного указателя. Это означает, что интеллектуальный указатель отвечает за удаление памяти, заданной необработанным указателем. Деструктор интеллектуального указателя содержит вызов для удаления, и поскольку интеллектуальный указатель объявлен в стеке, его деструктор вызывается, как только интеллектуальный указатель оказывается вне области, даже если исключение создается где-либо в другой части стека.

Доступ к инкапсулированному указателю осуществляется с помощью знакомых операторов указателя -> и *, которые класс интеллектуального указателя перегружает для возврата инкапсулированного необработанного указателя.

Этот интеллектуальный указатель C++ напоминает создание объектов в таких языках, как C#: вы создаете объект, а система удаляет его в правильный момент. Отличие заключается в том, что отсутствует отдельный сборщик мусора, работающий в фоновом режиме; память управляется через стандартные правила области C++, чтобы среда выполнения функционировала быстрее и эффективнее.

Важно!

Всегда создавайте интеллектуальные указатели в отдельной строке кода; ни в коем случае не делайте это в списке параметров, чтобы не произошла небольшая утечка ресурсов, связанная с определенными правилами выделения памяти спискам параметров.

В следующем примере показано, как можно использовать умный unique_ptr тип указателя из стандартной библиотеки 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.

В этом примере показаны следующие важные шаги, необходимые для использования интеллектуальных указателей.

  1. Объявите интеллектуальный указатель как автоматическую (локальную) переменную. (Не используйте newmalloc выражение в самом интеллектуальном указателе.)

  2. В параметре типа укажите тип, на который указывает инкапсулированный указатель.

  3. Передайте необработанный указатель newна объект -ed в конструкторе интеллектуального указателя. (Некоторые служебные функции или конструкторы интеллектуальных указателей делают это автоматически.)

  4. Используйте перегруженные операторы -> и * для доступа к объекту.

  5. Интеллектуальный указатель удаляет объект автоматически.

Интеллектуальные указатели разработаны для обеспечения максимальной эффективности в отношении памяти и производительности. Например, единственный элемент данных в unique_ptr — это инкапсулированный указатель. Это означает, что размер unique_ptr точно такой же, как и у указателя — 4 или 8 байтов. Доступ к инкапсулированному указателю с помощью перегруженного смарт-указателя * и операторы> не значительно медленнее, чем доступ к необработанным указателям напрямую.

Смарт-указатели имеют собственные функции-члены, к которым обращаются с помощью нотации dot. Например, некоторые смарт-указатели стандартной библиотеки C++ имеют функцию-член сброса, которая освобождает владение указателем. Это полезно, когда нужно освободить память, принадлежащую интеллектуальному указателю, не дожидаясь, пока интеллектуальный указатель окажется вне области, как показано в следующем примере.

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

}

Интеллектуальные указатели обычно предоставляют способ прямого доступа к необработанному указателю. Смарт-указатели стандартной библиотеки C++ имеют get функцию-член для этой цели и CComPtr имеют открытый p член класса. Предоставляя прямой доступ к базовому указателю, можно использовать интеллектуальный указатель для управления памятью в своем коде и по-прежнему передавать необработанный указатель коду, который не поддерживает интеллектуальные указатели.

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

Виды смарт-указателей

В следующем разделе приведены различные виды интеллектуальных указателей, доступные в среде программирования Windows, и приводится описание их использования.

Смарт-указатели стандартной библиотеки C++

Используйте эти интеллектуальные указатели как основной вариант для инкапсуляции указателей на простые старые объекты C++ (POCO).

  • unique_ptr
    Обеспечивает, чтобы у базового указателя был только один владелец. Используйте как вариант по умолчанию для POCO, кроме случая, когда вы точно знаете, что требуется shared_ptr. Может быть передан новому владельцу, но не может быть скопирован или сделан общим. Заменяет auto_ptr, использовать который не рекомендуется. Сравните с boost::scoped_ptr. unique_ptr является небольшим и эффективным; Размер является одним указателем, и он поддерживает ссылки rvalue для быстрого вставки и извлечения из коллекций стандартной библиотеки C++. Файл заголовка: <memory>. Дополнительные сведения см. в разделе "Практическое руководство . Создание и использование экземпляров unique_ptr" и класса unique_ptr.

  • shared_ptr
    Интеллектуальный указатель с подсчитанными ссылками. Используйте, когда необходимо присвоить один необработанный указатель нескольким владельцам, например, когда копия указателя возвращается из контейнера, но требуется сохранить оригинал. Необработанный указатель не будет удален до тех пор, пока все владельцы shared_ptr не выйдут из области или не откажутся от владения. Размер — 2 указателя; один — для объекта и второй — для блока общего элемента управления, который содержит счетчик ссылок. Файл заголовка: <memory>. Дополнительные сведения см. в статье "Создание и использование экземпляров shared_ptr" и класса shared_ptr.

  • weak_ptr
    Интеллектуальный указатель для особых случаев использования с shared_ptr. weak_ptr предоставляет доступ к объекту, который принадлежит одному или нескольким экземплярам shared_ptr, но не участвует в подсчете ссылок. Используйте, когда требуется отслеживать объект, но не требуется, чтобы он оставался в активном состоянии. Требуется в некоторых случаях для разрыва циклических ссылок между экземплярами shared_ptr. Файл заголовка: <memory>. Дополнительные сведения см. в статье "Создание и использование экземпляров weak_ptr" и класса weak_ptr.

Интеллектуальные указатели для COM-объектов (классическое программирование Windows)

При работе с COM-объектами создайте оболочку для указателей интерфейса в соответствующем типе интеллектуальных указателей. Библиотека шаблонных классов (ATL) определяет несколько интеллектуальных указателей для различных целей. Можно также использовать тип интеллектуального указателя _com_ptr_t, который компилятор использует при создании классов оболочки из файлов с расширением TLB. Это лучший вариант, если вы не хотите включать файлы заголовков ATL.

Класс CComPtr
Используйте, если невозможно использовать ATL. Выполняет подсчет ссылок с помощью методов AddRef и Release. Дополнительные сведения см. в разделе "Практическое руководство . Создание и использование экземпляров CComPtr и CComQIPtr".

Класс CComQIPtr
Похож на CComPtr, но также предоставляет упрощенный синтаксис для вызова QueryInterface COM-объекта. Дополнительные сведения см. в разделе "Практическое руководство . Создание и использование экземпляров CComPtr и CComQIPtr".

Класс CComHeapPtr
Интеллектуальный указатель на объекты, которые используют CoTaskMemFree для освобождения памяти.

Класс CComGITPtr
Интеллектуальный указатель для интерфейсов, получаемых из глобальной таблицы интерфейсов (GIT).

Класс _com_ptr_t
По функциональности аналогичен CComQIPtr, но не зависит от заголовков ATL.

Интеллектуальные указатели ATL для объектов POCO

Помимо смарт-указателей для ОБЪЕКТОВ COM, ATL также определяет интеллектуальные указатели и коллекции смарт-указателей для обычных старых объектов C++ (POCO). В классическом программировании Windows эти типы являются полезными альтернативами коллекциям стандартной библиотеки C++, особенно если переносимость кода не требуется или если вы не хотите смешивать модели программирования стандартной библиотеки C++ и ATL.

Класс CAutoPtr
Интеллектуальный указатель, принудительно реализующий уникальное владение путем переноса владения на копию. Сравним с нерекомендуемым классом std::auto_ptr.

Класс CHeapPtr
Интеллектуальный указатель для объектов, выделенных с помощью функции C malloc .

Класс CAutoVectorPtr
Интеллектуальный указатель для массивов, память для которых выделяется с помощью new[].

Класс CAutoPtrArray
Класс, инкапсулирующий массив элементов CAutoPtr.

Класс CAutoPtrList
Класс, инкапсулирующий методы для управления списком узлов CAutoPtr.

См. также

Указатели
Справочник по языку C++
Стандартная библиотека C++