Semenalidery.com

IT Новости из мира ПК
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Shared pointer c

std:: shared_ptr

Manages the storage of a pointer, providing a limited garbage-collection facility, possibly sharing that management with other objects.

Objects of shared_ptr types have the ability of taking ownership of a pointer and share that ownership: once they take ownership, the group of owners of a pointer become responsible for its deletion when the last one of them releases that ownership.

shared_ptr objects release ownership on the object they co-own as soon as they themselves are destroyed, or as soon as their value changes either by an assignment operation or by an explicit call to shared_ptr::reset. Once all shared_ptr objects that share ownership over a pointer have released this ownership, the managed object is deleted (normally by calling ::delete, but a different deleter may be specified on construction).

shared_ptr objects can only share ownership by copying their value: If two shared_ptr are constructed (or made) from the same (non-shared_ptr) pointer, they will both be owning the pointer without sharing it, causing potential access problems when one of them releases it (deleting its managed object) and leaving the other pointing to an invalid location.

Additionally, shared_ptr objects can share ownership over a pointer while at the same time pointing to another object. This ability is known as aliasing (see constructors), and is commonly used to point to member objects while owning the object they belong to. Because of this, a shared_ptr may relate to two pointers:

  • A stored pointer, which is the pointer it is said to point to, and the one it dereferences with operator*.
  • An owned pointer (possibly shared), which is the pointer the ownership group is in charge of deleting at some point, and for which it counts as a use.

Generally, the stored pointer and the owned pointer refer to the same object, but alias shared_ptr objects (those constructed with the alias constructor and their copies) may refer to different objects.

A shared_ptr that does not own any pointer is called an empty shared_ptr. A shared_ptr that points to no object is called a null shared_ptr and shall not be dereferenced. Notice though that an empty shared_ptr is not necessarily a null shared_ptr, and a null shared_ptr is not necessarily an empty shared_ptr.

shared_ptr objects replicate a limited pointer functionality by providing access to the object they point to through operators * and ->. For safety reasons, they do not support pointer arithmetics.

A related class, weak_ptr, is able to share pointers with shared_ptr objects without owning them.

Template parameters

Member types

The following alias is a member type of shared_ptr.

Записки программиста

Шпаргалка по использованию умных указателей в C++

26 февраля 2018

Благодаря наличию исключений, язык C++ позволяет разделить основную логику приложения и обработку ошибок, не мешая их в одну кучу. Что есть очень хорошо. Однако теперь по коду нельзя с уверенностью сказать, где может быть прервано его исполнение. Отсюда возникает опасность утечки ресурсов. Проблема эта решается при помощи деструкторов и идиомы RAII. Впрочем, придерживаться этой идиомы становится проблематично при использовании указателей. Особенно при использовании их не как членов класса, а просто как переменных в методах. На наше с вами счастье, в стандартной библиотеке языка есть умные указатели (smart pointers), придуманные именно для этого случая. Поскольку на C++ я пишу не регулярно, то иногда забываю некоторые нюансы использования умных указателей, в связи с чем решил вот набросать небольшую шпаргалку.

Важно! В старых книжках и статьях можно встретить упоминание auto_ptr. Этот тип умных указателей появился в C++, когда в языке еще не было move semantics. Из-за этого использование auto_ptr порой может приводить к трудным в обнаружении ошибкам. В стандарте C++17 auto_ptr был удален. Другими словами, все, что вы должны знать об auto_ptr — это то, что его не должно быть в современном коде. Вместо него всегда используйте unique_ptr.

unique_ptr

Шаблонный класс unique_ptr представляет собой уникальный указатель на объект. Указатель нельзя копировать, но можно передавать владение им с помощью std::move. При уничтожении указателя автоматически вызывается деструктор объекта, на который он указывает.

Создается unique_ptr так:

… но обычно используют шаблон make_unique, так короче:

Класс unique_ptr перегружает оператор -> , что позволяет обращаться к полям класса и вызывать его методы, словно мы работаем с обычным указателем:

Как уже отмечалось, unique_ptr запрещено копировать:

Однако владение им можно передать при помощи std::move, например:

Плюс к этому, мы всегда можем получать из unique_ptr обычный указатель на объект:

… хотя это и является code smell. Кроме того, ничто не мешает создавать ссылки (reference) на unique_ptr:

То есть, в этом случае мы как бы не отнимаем владение объектом, а ненадолго одалживаем его, обращаясь к нему через все тот же умный указатель.

Интересно, что unique_ptr позволяет указать функцию, которую он будет вызывать вместо деструктора, так называемый custom deleter. Это позволяет использовать unique_ptr с ресурсами, возвращаемых из библиотек для языка C, и даже реализовать аналог defer из языка Go:

/* g++ custom-deleter.cpp -o custom-deleter */

#include
#include
#include
#include

template
using auto_cleanup = std :: unique_ptr > ;

static char dummy [ ] = «» ;

#define _DEFER_CAT_(a,b) a##b
#define _DEFER_NAME_(a,b) _DEFER_CAT_(a,b)
#define defer(. )
auto _DEFER_NAME_(_defer_,__LINE__) =
auto_cleanup (dummy, [&](char*) < __VA_ARGS__; >);

int main ( ) <
auto_cleanup f (
fopen ( «test.txt» , «w» ) ,
[ ] ( FILE * f ) < fclose ( f ) ; >
) ;

defer ( std :: cout delete[] вместо delete . Если этого не сделать, будет освобожден только первый объект из массива, остальные же утекут.

shared_ptr и weak_ptr

Класс shared_ptr является указатем на объект, которым владеет сразу несколько объектов. Указатель можно как перемещать, так и копировать. Число существующих указателей отслеживается при помощи счетчика ссылок. Когда счетчик ссылок обнуляется, вызывается деструктор объекта. Сам по себе shared_ptr является thread-safe, но он не делает магическим образом thread-safe объект, на который ссылается. То есть, если доступ к объекту может осуществляться из нескольких потоков, вы должны не забыть предусмотреть в нем мьютексы или что-то такое.

Читать еще:  Sharepoint создание формы

Для создания shared_ptr обычно используется шаблон make_shared:

В остальном работа с ним мало отличается от работы с unique_ptr, за тем исключением, что shared_ptr можно смело копировать.

Интересные грабли при использовании shared_ptr заключается в том, что с его помощью можно создать циклические ссылки. Например, есть два объекта. Первый ссылается при помощи shared_ptr на второй, а второй — на первый. Даже если ни на один из объектов нет других ссылок, счетчики ссылок никогда не обнулятся, и объекты никогда не будут уничтожены.

Эта проблема обходится при помощи weak_ptr, так называемого слабого указателя. Класс weak_ptr похож на shared_ptr, но не участвует в подсчете ссылок. Также у weak_ptr есть метод lock() , возвращающий временный shared_ptr на объект. Пример использования:

class SomeClass <
public :
void sayHello ( ) <
std :: cout wptr ;

<
auto ptr = std :: make_shared ( ) ;
wptr = ptr ;

if ( auto tptr = wptr. lock ( ) ) <
tptr — > sayHello ( ) ;
> else <
std :: cout sayHello ( ) ;
> else <
std :: cout lock() класса weak_ptr, объект продолжит свое существование. Он будет уничтожен только тогда, когда на объект не останется вообще никаких указателей.

Умные указатели и наследование

Вопрос, о котором часто забывают — это кастование умных указателей вверх и вниз по иерархии классов. Для shared_ptr в стандартной библиотеке есть шаблоны static_pointer_cast, dynamic_pointer_cast и другие. Для unique_ptr таких же шаблонов почему-то не занесли, но их нетрудно найти на StackOverflow.

// https://stackoverflow.com/a/21174979/1565238
template
std :: unique_ptr
static_unique_ptr_cast ( std :: unique_ptr && p )
<
auto d = static_cast ( p. release ( ) ) ;
return std :: unique_ptr ( d,
std :: move ( p. get_deleter ( ) ) ) ;
>

template
std :: unique_ptr
dynamic_unique_ptr_cast ( std :: unique_ptr && p )
<
if ( Derived * result = dynamic_cast ( p. get ( ) ) ) <
p. release ( ) ;
return std :: unique_ptr ( result,
std :: move ( p. get_deleter ( ) ) ) ;
>
return std :: unique_ptr ( nullptr, p. get_deleter ( ) ) ;
>

class Base <
public :
Base ( int num ) : num ( num ) < >;

virtual void sayHello ( ) <
std :: cout ( 1 ) ;
derived — > sayHello ( ) ;

std :: unique_ptr base = std :: move ( derived ) ;
base — > sayHello ( ) ;

auto derived2 = static_unique_ptr_cast ( std :: move ( base ) ) ;
derived2 — > sayHello ( ) ;

std :: unique_ptr base2 = std :: make_unique ( 2 ) ;
base2 — > sayHello ( ) ;

std :: cout ( 1 ) ;
derived — > sayHello ( ) ;

auto base = std :: static_pointer_cast ( derived ) ;
base — > sayHello ( ) ;

auto derived2 = std :: static_pointer_cast ( base ) ;
derived2 — > sayHello ( ) ;

std :: shared_ptr base2 = std :: make_shared ( 2 ) ;
base2 — > sayHello ( ) ;

int main ( ) <
testUnique ( ) ;
testShared ( ) ;
>

Как видите, все оказалось не так уж и сложно.

Заключение

По моим представлениям, приведенной шпаргалки должно хватать в

Boost shared pointers

Содержание

Notice: C++11

C++11 offers unique_ptr and shared_ptr, and should be used instead. Please check if we have switched to C++11 and if so, start using those. Search google for it, you’ll find a lot of text about this great addition to the C++ standard.

Boost shared pointers

The Boost library is a set of peer-reviewed C++ libraries that play well with STL, and some of them are considered for inclusion in future versions of the C++ standard. They are generally regarded as well designed, useful, and powerful. They are also cross platform. All header-only Boost libraries can be used in Inkscape, since Boost is an accepted compile time dependency.

This short tutorial will deal with the use of Boost shared pointers. They can be used to significantly reduce the amount of code needed for efficient memory management. Shared pointers with semantics identical to Boost ones are part of the C++ Library Technical Report 1, and will be included as part of the standard library in a future revision of C++.

If the ownership of objects is clear, investigate whether the simpler and lower overhead std::auto_ptr or boost::scoped_ptr can meet your needs. If you mainly need to use shared_ptr to put smart pointers in containers (since std::auto_ptr cannot be used in this way), investigate Boost pointer containers like boost::ptr_list.

Rationale

The memory management problem is how to guarantee that a program does not have any memory leaks. It can be solved with simple reference conuting if there are no cycles between objects. There are two approaches to solve it when :

  1. Garbage collection. Objects are created and left alone when no longer needed. When the memory is low, a background process kicks in and reclaims memory used by objects that can no longer be reached from the stack. This is the method used in most scripting languages, as well as Java and CLR. However, it has some drawbacks. Firstly, destructors can run at some indeterminate time after the object becomes unused, or never. This precludes using the RAII idiom. Secondly, there is some memory overhead associated with maintaining a garbage-collected heap. Thirdly, it requires some type information to be preserved at runtime, to determine where the garbage collector should look for object references — otherwise unrelated data can alias pointers, preventing some objects from being colleted.
  2. Shared and weak pointers. This approach uses two types of object references to break the cycles. A shared pointer will keep the pointed object in memory, while a weak pointer can be used to obtain a shared pointer to the same object, but will not prevent the object from destruction. This approach requires more programming effort and adds some (negligible) overhead to each operation on the shared pointer, but makes object destruction predictable.

The second method suits C++ better than garbage collection. Firstly, there is generally little to no type information present at runtime to determine where to look for pointers to objects; Garbage collectors for C++ and C must assume that all data on the stack and all contents of the objects’ memory are pointers to other objects (also known as conservative garbage collection). This can cause some unused memory to never be freed, because some integer in some object happens to have the same value as some other object’s address. Secondly, since the C++ standard library and other libraries frequently use the RAII idiom, many C++ objects (like std::fstream) have non-trivial destructors. If those destructors are not run right after the object becomes unused, the program may hold on to resources it no longer uses, like file descriptors or database connections. Finally, using tricks like storing the color of a red-black tree node in the low bit of a pointer can in theory cause objects to be collected before they become unused, bu this can be mitigated with anchoring.

Читать еще:  Sharepoint шаблоны сайтов

Shared pointers

Shared pointer is a smart pointer (a C++ object wih overloaded operator*() and operator->()) that keeps a pointer to an object and a pointer to a shared reference count. Every time a copy of the smart pointer is made using the copy constructor, the reference count is incremented. When a shared pointer is destroyed, the reference count for its object is decremented. Shared pointers constructed from raw pointers initially have a reference count of 1. When the reference count reaches 0, the pointed object is destroyed, and the memory it occupies is freed. You do not need to explicitly destroy objects: it will be done automatically when the last pointer’s destructor runs.

Usage

When creating a new object with new, use it as a constructor argument of a boost::shared_ptr.

You can reassign the pointer to a new object using the member function reset(). This will decrease the reference count of the old object, if any, and reinitialize the object with the argument and a reference count of 1.

Note however that you cannot obtain a shared pointer to an object from a raw pointer. This will cause a segmentation fault:

That’s because the temporary fptr is initialized with a reference count of 1. After the function returns, the reference count is zero and the object is destroyed. foo->something() will then fail, because foo no longer points to a valid object.

An uninitialized shared pointer is empty. This is equivalent to NULL for raw pointers. You can check for an empty shared pointer using this code:

A raw pointer can be retrieved using the get() method.

Weak pointers

Weak pointers can only be used to obtain a shared pointer to the same object, and check whether the object was already destroyed. They can be used to break circular references between objects. A weak pointer that points to an object that was already destroyed is called expired. Empty weak pointers (that don’t point to any object) are also expired.

Here is an example:

Weak pointers cannot be dereferenced for thread safety reasons. If some other thread destroyed the object after you checked the weak pointer for expiry but before you used it, you would get a crash. Shared pointers can be obtained from weak pointers using use of two methods:

  1. boost::shared_ptr fptr = weakfoo.lock(); — fptr will be empty if weakfoo is expired. An exception will never be thrown.
  2. boost::shared_ptr fptr(weakfoo); — the exception std::tr1::bad_weak_ptr will be thrown if weakfoo is expired.

Shared pointers in member functions

Sometimes a shared pointer to the current object is needed in its member function. Boost provides a mixin template class called enable_shared_from_this, which defines a no-argument member function called shared_from_this(). It returns a shared pointer to this. At least one instance of a shared pointer to this object must exist before the first use of this method; otherwise it has undefined results (usually crash). The best way to guarantee that this is condition met is to make the constructor protected and provide a factory method that returns a shared pointer to the newly created object.

kalnitsky’s way

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

Smart pointer — это объект, работать с которым можно как с обычным указателем, но при этом, в отличии от последнего, он предоставляет некоторый дополнительный функционал (например, автоматическое освобождение закрепленной за указателем области памяти).

Умные указатели призваны для борьбы с утечками памяти, которые сложно избежать в больших проектах. Они особенно удобны в местах, где возникают исключения, так как при последних происходит процесс раскрутки стека и уничтожаются локальные объекты. В случае обычного указателя — уничтожится переменная-указатель, при этом ресурс останется не освобожденным. В случае умного указателя — вызовется деструктор, который и освободит выделенный ресурс.

В новом стандарте появились следующие умные указатели: unique_ptr, shared_ptr и weak_ptr. Все они объявлены в заголовочном файле .

unique_ptr

Этот указатель пришел на смену старому и проблематичному auto_ptr. Основная проблема последнего заключается в правах владения. Объект этого класса теряет права владения ресурсом при копировании (присваивании, использовании в конструкторе копий, передаче в функцию по значению).

Это очень неудобно, при работе с контейнером из умных указателей. Банальное

сделает элемент вектора невалидным. Именно поэтому данный класс не пользовался популярностью среди разработчиков.

В отличии от auto_ptr, unique_ptr запрещает копирование.

Изменение прав владения ресурсом осуществляется с помощью вспомогательной функции std::move (которая является частью механизма перемещения).

Как auto_ptr, так и unique_ptr обладают методами reset(), который сбрасывает права владения, и get(), который возвращает сырой (классический) указатель.

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

shared_ptr

Это самый популярный и самый широкоиспользуемый умный указатель. Он начал своё развитие как часть библиотеки boost. Данный указатель был столь успешным, что его включили в C++ Technical Report 1 и он был доступен в пространстве имен tr1std::tr1::shared_ptr<> .

Читать еще:  Как увеличить размер слайда в powerpoint

В отличии от рассмотренных выше указателей, shared_ptr реализует подсчет ссылок на ресурс. Ресурс освободится тогда, когда счетчик ссылок на него будет равен 0. Как видно, система реализует одно из основных правил сборщика мусора.

Также как и unique_ptr, и auto_ptr, данный класс предоставляет методы get() и reset().

При работе с умным указателем, следует опасаться их создания на лету. Например, следующий код может привести к утечки памяти.

Почему? Да потому, что стандарт C++ не определяет порядок вычисления аргументов. Может случиться так, что сначала выполнится new Foo, затем getRandomKey() и лишь затем конструктор shared_ptr. Если же функция getRandomKey() бросит исключение, до конструктора shared_ptr дело не дойдет, хотя ресурс (объект Foo) был уже выделен.

В случае с shared_ptr есть выход — использовать фабричную функцию std::make_shared<> , которая создает объект заданного типа и возвращает shared_ptr указывающий на него.

Почему и как это работает? Очень просто. Как я уже сказал выше, make_shared возвращает shared_ptr. Этот результат является временным объектом, а стандарт C++ четко декларирует, что временные объекты уничтожаются, в случае появления исключения.

К слову, new Foo тоже возвращает временный объект. Однако, временным является указатель на выделенный ресурс, и в случае исключения — уничтожится указатель, при этом ресурс останется выделенным.

weak_ptr

Этот указатель также, как и shared_ptr начал свое рождение в проекте boost, затем был включен в C++ Technical Report 1 и, наконец, пришел в новый стандарт.

Данный класс позволяет разрушить циклическую зависимость, которая, несомненно, может образоваться при использовании shared_ptr. Предположим, есть следующая ситуация (переменные-члены не инкапсулированы для упрощения кода)

Как видно, объект foo ссылается на bar и наоборот. Образован цикл, из-за которого не вызовутся деструкторы объектов. Для того чтобы разорвать этот цикл, достаточно в классе Bar заменить shared_ptr на weak_ptr.

Почему образован цикл? Давайте разберемся. При выходе из блока (в данном случае функции main()) уничтожаются локальные объекты. Локальным объектом является foo. При уничтожении foo счетчик ссылок на его ресурс уменьшится на единицу. Однако, ресурс освобожден не будет, так как на него есть ссылка со стороны ресурса bar. А на bar есть ссылка со стороны того же ресурса foo.

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

Вместо заключения

Умные указатели — очень удобная и полезная вещь, но я рассмотрел их поверхностно, лишь их концептуальные части. За полным списком их возможностей следует обращаться к документации.

Стоит отметить, что рассмотренные мною умные указатели (кроме unique_ptr) не предназначен для владения массивами. Это связано с тем, что деструктор вызывает именно delete, а не delete[] (что требуется для массивов).

Для unique_ptr мы имеем дело с предопределенной специализацией для массивов. Для ее использования необходимо указать [] возле параметра шаблона. Выглядит это так.

Кроме этого, в boost есть специальный класс shared_array<>, но он в новый стандарт включен не был.

Умные указатели в C++

Умные указатели — это классы-обертки для обычных указателей C++. Они позволяют забыть о ручном освобождении памяти с помощью delete .

Классы умных указателей основаны на принципе RAII — получение ресурса есть инициализация. В случае указателей этот принцип сводится к вызову delete в деструкторе класса-обертки. Сам класс-обертка является локальной переменной, поэтому при выходе из области видимости ресурс (память в куче) автоматически освобождается.

В этом коде имеется существенный недостаток. Если в функции anotherFunc() между вызовом func() и delete будет выброшено исключение, то память мы так и не освободим. Просто не дойдем до delete .

Класс std::unique_ptr в C++

Первый вариант исправления ситуации:

Теперь нам не нужно самим думать о вызове delete . Все сделает std::unique_ptr . При этом использование такого указателя практически ничем не отличается от применения обычного.

Обратите внимание, что для использования умных указателей вам нужен компилятор с поддержкой C++11.

Особенность std::unique_ptr заключается в том, что он никому так просто не отдаст указатель, которым управляет:

Но мы можем явно указать, что хотим перенести управление указателем из одного unique_ptr в другой:

После этого переменная с становится пустой, а монопольное управление указателем получает c2 .

Реклама

Класс std::shared_ptr в C++

Но если нам нужно иметь несколько указателей на один и тот же объект? Для этого воспользуемся std::shared_ptr :

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

Реклама

Умный указатель в качестве возвращаемого значения

Еще лучше, если функция func() явно будет возвращать умный указатель вместо обычного:

Это обяжет использовать безопасную конструкцию всех пользователей вашей функции.

В зависимости от ситуации может потребоваться вернуть shared_ptr , а не unique_ptr :

Обратите внимание, что для создания shared_ptr мы использовали шаблонную функцию std::make_shared() . В качестве параметра шаблона она принимает имя класса, а ее аргументы будут использованы при вызове конструктора создаваемого объекта.

Умные указатели для массивов

Оба класса умных указателей можно использовать для управления массивами:

Заметим, что при создании shared_ptr потребовался дополнительный аргумент std::default_delete () . Он необходим для корректного освобождения ресурсов, обеспечивая вызов delete[] .

С другой стороны, вряд ли найдется веское основание, чтобы использовать подобные конструкции. Лучше применять std::array или std::vector .

Когда использовать std::shared_ptr, а когда std::unique_ptr

На самом деле, все следует из названия этих классов. Если объект нужен только в одном месте, то используйте std::unique_ptr (чтобы защититься от непреднамеренного копирования). Если объект понадобился в нескольких местах, то — std::shared_ptr .

Если же проводить более глубокий анализ, то выясняется, что std::unique_ptr по своей эффективности очень близок к обычным указателям. Чего нельзя сказать о std::shared_ptr . Он предоставляет больше возможностей, но за все приходится платить. Увеличивается и расход памяти, и время доступа. Однако накладные расходы не столь существенны, поэтому в большинстве приложений разница окажется незаметной.

Ссылка на основную публикацию
Adblock
detector