Поделиться через


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

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

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

Интеллектуальные указатели определены в пространстве имен std в файле заголовка <memory>. Они чрезвычайно важны для идиомы программирования RAII или Resource Acquisition Is Initialialization — получение ресурса является инициализацией. Главная задача этой идиомы — обеспечить, чтобы одновременно с получением ресурса производилась инициализация объекта, чтобы все ресурсы для объекта создавались и подготавливались в одной строке кода. На практике основным принципом 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 из стандартной библиотеки шаблонов может использоваться для инкапсуляции указателя на большой объект.

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. Объявите интеллектуальный указатель как автоматическую (локальную) переменную. (Не используйте выражение new или malloc для самого интеллектуального указателя.)

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

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

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

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

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

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

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

}

Интеллектуальные указатели обычно предоставляют способ прямого доступа к необработанному указателю. Интеллектуальные указатели STL для этой цели содержат функцию-член 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 для быстрой вставки и извлечения из коллекций STL. Файл заголовка: <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.

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

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

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

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

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

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

См. также

Другие ресурсы

Возвращение к C++ (современный C++)

Справочник по языку C++

Справочник по стандартной библиотеке C++

общие сведения: Управление памятью в C++