MyTetra Share
Делитесь знаниями!
Как пользоваться умными указателями в С++. Краткая памятка по умным указателям
Время создания: 09.09.2019 02:16
Автор: xintrea
Текстовые метки: c++, умные, указатели, интеллектуальные, unique_ptr, shared_ptr, weak_ptr, make_shared()
Раздел: Компьютер - Программирование - Язык C++ (Си++) - Стандарт C++11 и выше
Запись: xintrea/mytetra_syncro/master/base/1550668138mdaxtdb78a/text.html на raw.github.com

В языке C++11 было введено три типа интеллектуальных указателей: unique_ptr, shared_ptr, weak_ptr. Интеллектуальный указатель типа auto_ptr, введенный в C++03, считается устаревшим, и использовать его крайне не рекомендуется. Он считается неудачной попыткой в реализации указателя unique_ptr.


Вот в каких случаях нужно использовать тот или иной указатель:



unique_ptr: должен использоваться, когда ресурс памяти не должен был разделяемым (у этого указателя нет конструктора копирования), но ресурс может быть передан во владение другому unique_ptr


shared_ptr: должен использоваться, когда ресурс памяти должен быть разделяемым (имеется в виду, что когда на одну и ту же область памяти может указывать несколько указателей именно shared_ptr типа).


weak_ptr: содержит ссылку на объект, которым управляет shared_ptr, но не осуществляет подсчет ссылок; позволяет избавиться от циклической зависимости



Для всех этих типов указателей определен оператор "звездочка" *, который, как и в случае обычных указателей, дает доступ к содержимому указателя (разыменовывает указатель).


Каждый тип указателя имеет метод get(), который возвращает обычный указатель. Таким образом, ничего не стоит написать код, в котором выясняется обычный указатель, и потом с ним происходит некорректная работа. То есть, все эти типы указателей не могут гарантировать, что работа с выделенной для их объектов памятью будет правильной. По сути, все эти типы указателей - это просто инструмент, позволяющий структурировать работу с указателями, но не исключающий возможность "выстрела себе в ногу".



unique_ptr


Указатель unique_ptr имеет конструктор с модификатором explicit, из-за которого присвоить значение этому указателю можно только в момент объявления (т.е. инициализация такого умного указателя должна быть в момент объявления). Сразу возникает вопрос: а как же тогда присвоить значение умному указателю, если он является полем класса? Ведь его объявление делается, например, в заголовочном *.h-файле, а присвоение надо делать в коде релизации класса в *.cpp-файле.


Для решения этой проблемы есть две возможности. Первая: проинициализировать указатель в списке инициализации класса:



Файл заголовка (*.h):


class MyClass

{

unique_ptr<QLabel> pLabel;

};


Файл реализации (*.cpp):


MyClass::MyClass() : pLabel(new QLabel("Text"))

{


}



Вторая возможность - это воспользоваться в произвольном месте кода методом make_unique:



make_unique<SomeClass>(параметры конструктора)



Например:



pLabel=make_unique<QLabel>("Text");



Да, необычный синтаксис. Хотелось бы написать что-то более привычное:


p=make_unique<QLabel>(new QLabel("Text"));


но так компилироваться не будет, потому что make_unique - это шаблонная функция, которой в угловых скобках передается имя создаваемого типа (в нашем случае - имя класса), а в круглых скобках - параметры конструктора.



Кстати, одному и тому же указателю unique_ptr можно последовательно присвоить указатели на разные объекты:



pLabel=make_unique<QLabel>("Text");

pLabel=make_unique<QLabel>("Another text");



Что при этом произойдет? Будет ли утечка памяти для первого объекта? Нет, утечки не будет. Если умный указатель unique_ptr видит, что он указывает на некий объект, а ему дают команду указывать на другой объект, он вначале удалит первый объект и начнет указывать на другой.


Для передачи владения объектом другому unique_ptr, используется метод std::move(). После передачи владения, интеллектуальный указатель, который передал владение, становится нулевым и get() вернет nullptr.



void printValueByPointer(int* p)

{

std::cout << *p << std::endl;

}


std::unique_ptr<int> p1(new int(42));

std::unique_ptr<int> p2 = std::move(p1); // Передача владения другому указателю


if(p1)

printValueByPointer(p1.get());


(*p2)++; // Хранимое значение становится равным 43


if(p2)

printValueByPointer(p2.get());



Еще одна особенность unique_ptr - это то, что при выходе из области видимости у него, как и у любого обычного объекта, будет вызван деструктор. А в деструкторе размещены команды удаления объекта, на который указывает данный умный указатель. Таким образом, управление временем жизни объекта, на который указывает указатель unique_ptr, происходит автоматически. И нет никакого смысла следить за тем, чтобы память, выделенная под объект, на который указывает этот умный указатель, была вручную освобождена.



shared_ptr


Следующий пример демонстрирует shared_ptr. Использование похоже на unique_ptr, хотя семантика отличается, поскольку теперь владение совместно используемое:



void first(int* p)

{

}


void second(std::shared_ptr<int> p)

{

++(*p);

}


std::shared_ptr<int> p1( new int(42) );

std::shared_ptr<int> p2 = p1;

second(p1); // Передача в функцию умного указателя shared_ptr в явном виде

first(p2.get()); // Передача в функцию обычного указателя, куда указывает умный указатель



Объявление указателя p1 эквивалентно следующей записи:



auto p1 = std::make_shared<int>(42); // Так писать предпочтительней



make_shared() — это функция, имеющая преимущество при выделении памяти для совместно используемого объекта и интеллектуального указателя с единственным выделением, в отличие от явного получения shared_ptr через конструктор, где требуется, по крайней мере, два выделения. Из-за этого может произойти утечка памяти. В следующем примере как раз это демонстрируется, что утечка может произойти в случае, если seed() бросит исключение:



void myFunction(std::shared_ptr<int> p, int init)

{

*p = init;

}


myFunction(std::shared_ptr<int>(new int(42)), seed());



Эта проблема решается использованием make_shared().



weak_ptr


И, наконец, умный указатель weak_ptr. Заметьте, что вы должны получить shared_ptr для объекта, вызывая lock(), чтобы получить доступ к объекту (блин, что имел в виду автор? это предложение непонятно):



auto p = std::make_shared<int>(42);

std::weak_ptr<int> wp = p;


{

auto sp = wp.lock();

std::cout << *sp << std::endl;

}


p.reset();


if(wp.expired())

std::cout << "expired" << std::endl;



Вообще, weak_ptr должен использоваться только в том случае, если в программе есть "кольцевая" или "перекрестная" зависимость указателей. Если нужно разорвать такую зависимость, тогда вместо одного из shared_ptr, можно использовать weak_ptr.



Примечание: подробнее про семантику владения и переменщения ресурсов можно прочитать в статье Семантика копирования-владения и управление ресурсами в C++.


Так же в этом разделе:
 
MyTetra Share v.0.59
Яндекс индекс цитирования