MyTetra Share
Делитесь знаниями!
Как пользоваться умными указателями в С++. Краткая памятка по умным указателям
20.02.2019
16:08
Автор: xintrea
Текстовые метки: c++, умные, указатели, интеллектуальные, unique_ptr, shared_ptr, weak_ptr, make_shared()
Раздел: Компьютер - Программирование - Язык C++

В языке 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<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 foo(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)

foo(p1.get());


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


if(p2)

foo(p2.get());



shared_ptr


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



void foo(int* p)

{

}


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

{

++(*p);

}


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

std::shared_ptr<int> p2 = p1;

bar(p1);

foo(p2.get());



Первое объявление эквивалентно следующему:



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



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



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

{

*p = init;

}


foo(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;



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