|
|||||||
Как пользоваться умными указателями в С++. Краткая памятка по умным указателям
Время создания: 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++. |
|||||||
Так же в этом разделе:
|
|||||||
|
|||||||
|