Semenalidery.com

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

Smart pointer c

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

В отличии от рассмотренных выше указателей, 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<>, но он в новый стандарт включен не был.

Smart developers use smart pointers (1/7) – Smart pointers basics

One thing that can rapidly clutter your C++ code and hinder its readability is memory management. Done badly, this can turn a simple logic into an inexpressive slalom of mess management, and make the code lose control over memory safety.

The programming task of ensuring that all objects are correctly deleted is very low in terms of levels of abstraction, and since writing good code essentially comes down to respecting levels of abstraction, you want to keep those tasks away from your business logic (or any sort of logic for that matter).

Smart pointers are made to deal with this effectively and relieve your code from the dirty work. This series of posts will show you how to take advantage of them to make your code both more expressive and more correct.

We’re going to go deep into the subject and since I want everyone to be able to follow all of this series, there is no prerequisite and we start off here with the basics of smart pointers.

Here is the content of the series:

The stack and the heap

Like many other languages, C++ has several types of memories, that correspond to different parts of the physical memory. They are: the static, the stack, and the heap. The static is a topic rich enough to deserve its own moment of glory, so here we focus on the stack and the heap only.

The stack

Allocating on the stack is the default way to store objects in C++:

There is one important, crucial, even fundamental thing to know about the stack though. It is at the basis of everything that follows in the rest of this series. And the good news is that it’s very easy:

Objects allocated on the stack are automatically destroyed when they go out of scope.

You can re-read this a couple of times, maybe tatoo it on your forearm if needed, and print out a T-shirt to your spouse reading this statement so that you can be reminded of it regularly.

In C++ a scope is defined by a pair of brackets ( < and >) except those used to initialize objects:

And there are 3 ways for an object to go out of scope:

  • encountering the next closing bracket ( > ),
  • encountering a return statement,
  • having an exception thrown inside the current scope that is not caught inside the current scope.

So in the first code example, s is destroyed at the closing bracket of the if statement, and a is destroyed at the return statement of the function.

The heap

The heap is where dynamically allocated objects are stored, that is to say objects that are allocated with a call to new, which returns a pointer:

After the above statement, pi points to an int object allocated on the heap.

Ok strictly speaking, the memory allocated by new is called the free store. The heap is the memory allocated by malloc, calloc and realloc which are vestiges from C that are normally no longer used in new code, and which we are ignoring in this post (but we’ll talk more about them later in the series). But the term ‘heap’ is so ubiquitous in developer jargon to talk about any dynamically allocated memory that I am using it here in that sense.

Anyway to destroy an object allocated by new, we have to do it manually by calling delete:

Contrary to the stack, objects allocated on the heap are not destroyed automatically. This offers the advantages of keeping them longer than the end of a scope, and without incurring any copy except those of pointers which are very cheap. Also, pointers allow to manipulate objects polymorphically: a pointer to a base class can in fact point to objects of any derived class.

But as a price to pay for this flexibility it puts you, the developer, in charge of their deletion.

Читать еще:  Sharepoint service desk

And deleting a object on the heap is no trivial task: delete has to be called once and only once to deallocate a heap-based object. If it is not called the object is not deallocated, and its memory space is not reusable – this is called a memory leak. But on the other hand, a delete called more than once on the same address leads to undefined behaviour.

And this is where the code gets cluttered and loses expressiveness (and sometimes even correctness). Indeed, to make sure that all objects are correctly destroyed, the bookkeeping varies from a simple delete to a complex system of flags in the presence of early returns for example.

Also, some interfaces are ambiguous in terms of memory management. Consider the following example:

As a caller of this function, should I delete the pointer it returns? If I don’t and no one does then it’s a memory leak. But if I do and someone else does, then it’s undefined behaviour. Between the devil and the deep blue sea.

I think all this has led to a bad reputation of C++ as being a complex language in terms of memory management.

But fortunately, smart pointers will take care of all of this for you.

RAII: the magic four letters

RAII is a very idiomatic concept in C++ that takes advantage of the essential property of the stack (look up on your arm, or at the upper body of your spouse) to simplify the memory management of objects on the heap. In fact RAII can even be used to make easy and safe the management of any kind of resource, and not only memory. Oh and I’m not going to write what these 4 letters mean because it is unimportant and confusing in my opinion. You can take them as the name of someone, like superhero of C++ for example.

The principle of RAII is simple: wrap a resource (a pointer for instance) into an object, and dispose of the resource in its destructor. And this is exactly what smart pointers do:

Умные указатели в 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 . Он предоставляет больше возможностей, но за все приходится платить. Увеличивается и расход памяти, и время доступа. Однако накладные расходы не столь существенны, поэтому в большинстве приложений разница окажется незаметной.

Урок №189. Умные указатели и Семантика перемещения

Обновл. 12 Янв 2020 |

В этом уроке мы рассмотрим, что такое умные указатели и семантика перемещения в С++, а также как это всё используется на практике.

Проблема

Хотя код выше кажется довольно простым, можно очень легко забыть в конце освободить память, выделенную для ptr . Даже если вы не забудете это сделать, существует множество способов, из-за которых ptr не будет удалён. Это может произойти через досрочный возврат return:

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

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

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

Так что же, выходом является использование класса для управления указателями и выполнения соответствующей очистки памяти? Да, именно так!

Например, рассмотрим класс, единственными задачами которого является хранение и «управление» переданным ему указателем, а затем корректное освобождение памяти при выходе объекта класса из области видимости. До того момента, пока объекты этого класса создаются как локальные переменные, мы можем гарантировать, что как только они выйдут из области видимости (независимо от того, когда или как), переданный указатель будет уничтожен.

Вот первый набросок:

Результат выполнения программы выше:

Item acquired
Item destroyed

Рассмотрим детальнее, как работают эти программа и класс. Сначала мы динамически выделяем объект класса Item и передаём его в качестве параметра нашему шаблону класса Auto_ptr1. С этого момента объект item класса Auto_ptr1 владеет выделенным объектом класса Item (Auto_ptr1 имеет композиционную связь с m_ptr ). Поскольку item объявлен в качестве локальной переменной и имеет область видимости блока, он выйдет из области видимости после завершения выполнения блока, в котором находится, и будет уничтожен. А поскольку это объект класса, то при его уничтожении будет вызван деструктор Auto_ptr1. Этот деструктор и обеспечит удаление указателя Item, который он хранит!

До тех пор, пока объект класса Auto_ptr1 определён как локальная переменная (с автоматической продолжительностью, отсюда и часть «Auto» в имени класса), Item гарантированно будет уничтожен в конце блока, в котором он объявлен, независимо от того, как этот блок (функция main()) завершит своё выполнение (досрочно или нет).

Такой класс называется умным указателем. Умный указатель — это класс, предназначенный для управления динамически выделенной памятью и обеспечения освобождения (удаления) выделенной памяти при выходе объекта этого класса из области видимости. Соответственно, встроенные (обычные) указатели иногда ещё называют «глупыми указателями», так как они не могут выполнять после себя очистку памяти.

Теперь вернёмся к нашему примеру с myFunction() выше и покажем, как использование класса умного указателя сможет решить нашу проблему:

Если пользователь введёт ненулевое целое число, то результат выполнения программы выше:

Item acquired
Enter an integer: 7
Hi!
Item destroyed

Если же пользователь введёт ноль, то функция myFunction() завершит своё выполнение досрочно и мы увидим:

Item acquired
Enter an integer: 0
Item destroyed

Обратите внимание, даже в случае, когда пользователь введёт ноль и функция завершит своё выполнение досрочно, Item по-прежнему будет корректно удалён.

Поскольку переменная ptr является локальной переменной, то она уничтожается при завершении выполнения функции (независимо от того, как это будет сделано: досрочно или нет). И поскольку деструктор Auto_ptr1 выполняет очистку Item, то мы можем быть уверены, что Item будет корректно удалён.

Критический недостаток

Класс Auto_ptr1 выше имеет критическую ошибку, которая скрывается за некоторым автоматически генерируемым кодом. Прежде чем читать дальше, посмотрите, сможете ли вы определить, что это за ошибка.

Читать еще:  C point com

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

Хорошо, время истекло.

Мы не будем сейчас вам это говорить, мы сейчас вам это покажем:

Результат выполнения программы выше:

Item acquired
Item destroyed
Item destroyed

Очень вероятно (но не обязательно), что в нашей программе произойдёт сбой именно в этот момент. Нашли проблему? Поскольку мы не предоставили конструктор копирования или свой оператор присваивания (перегрузку оператора присваивания), то C++ предоставил их самостоятельно. И то, что он предоставил, выполняет поверхностное копирование. Поэтому, когда мы инициализируем item2 значением item1 , оба объекта класса Auto_ptr1 указывают на один и тот же Item. Когда item2 выходит из области видимости, он удаляет Item, оставляя item1 с висячим указателем. Когда же item1 отправляется на удаление своего (уже удаленного) Item, происходит «Бум!».

Вы получите ту же проблему, используя следующую функцию:

В этой программе item1 передаётся по значению в параметр item функции passByValue, что приведёт к дублированию указателя Item. Мы получим «Бум!» опять.

Так быть не должно. Что мы можем сделать?

Мы можем явно определить и удалить конструктор копирования и оператор присваивания, тем самым предотвращая выполнение любого копирования. Это также предотвратит передачу по значению.

Но как нам тогда вернуть Auto_ptr1 из функции обратно в caller?

Мы не можем вернуть Auto_ptr1 по ссылке, так как локальный Auto_ptr1 будет уничтожен в конце функции, и в caller передастся ссылка, которая будет указывать на удалённую память. Передача по адресу имеет ту же проблему. Мы могли бы вернуть указатель item по адресу, но потом мы могли бы забыть удалить item позже, что является основным смыслом использования умных указателей. Так что возврат Auto_ptr1 по значению — это единственная опция, которая имеет смысл, но тогда мы получим поверхностное копирование, дублирование указателей и «Бум!».

Другой вариант — переопределить конструктор копирования и оператор присваивания для выполнения глубокого копирования. Таким образом, мы, по крайней мере, гарантированно избежим дублирование указателей (которые будут указывать на один и тот же объект). Но глубокое копирование может быть затратной операцией (а также нежелательной или даже невозможной), и мы не хотим делать ненужные копии объектов просто для того, чтобы возвратить Auto_ptr1 из функции. Плюс присваивание или инициализация глупого указателя не копирует объект, на который указывает, так почему же мы ожидаем, что умные указатели будут вести себя по-другому?

Семантика перемещения

А что, если бы наш конструктор копирования и оператор присваивания не копировали указатель (семантика копирования), а передавали владение указателем из источника в объект назначения? Это основная идея семантики перемещения. Семантика перемещения означает, что класс, вместо копирования, передаёт право собственности на объект.

Давайте обновим наш класс Auto_ptr1 с использованием семантики перемещения:

Обратите внимание, перегруженный operator= передаёт право собственности на m_ptr от item1 к item2 ! Следовательно, у нас не выполняется дублирование указателей, и всё аккуратно очищается (удаляется).

std::auto_ptr и почему его лучше не использовать

Теперь самое время поговорить об std::auto_ptr. std::auto_ptr, представленный в C++98, был первой попыткой C++ сделать стандартизированный умный указатель. В std::auto_ptr решили реализовать семантику перемещения точно так же, как это сделано в классе Auto_ptr2.

Однако, std::auto_ptr (как и наш класс Auto_ptr2) имеет ряд проблем, которые делают его использование опасным.

Во-первых, поскольку std::auto_ptr реализовывает семантику перемещения через конструктор копирования и оператор присваивания, то передача std::auto_ptr в функцию по значению приведёт к тому, что ваш Item будет перемещен в параметр функции и, следовательно, будет уничтожен в конце функции, когда параметры этой функции выйдут из области видимости (в нашем классе Auto_ptr2 передача выполняется по ссылке). Затем, когда вы попытаетесь получить доступ к аргументу std::auto_ptr из caller-а (не осознавая, что он был передан и удалён), вы внезапно выполните разыменование нулевого указателя. Бум!

Во-вторых, std::auto_ptr всегда удаляет своё содержимое, используя delete, которое не работает с массивами. Это означает, что std::auto_ptr не будет правильно работать с динамическими массивами, поскольку использует неправильный тип удаления. Хуже того, std::auto_ptr не помешает вам передать ему динамический массив, который затем будет неправильно обработан, что приведёт к утечкам памяти.

Наконец, std::auto_ptr не очень хорошо работает со многими другими классами из стандартной библиотеки С++ (особенно с контейнерными классами и классами алгоритмов). Это происходит из-за того, что классы стандартной библиотеки С++ предполагают, что, когда они копируют элемент, они фактически выполняют копирование, а не перемещение.

Из-за вышеупомянутых недостатков в C++11 перестали использовать std::auto_ptr, а в C++17 планировали удалить его из стандартной библиотеки С++.

Правило: std::auto_ptr устарел и не должен использоваться. (Используйте вместо него std::unique_ptr или std::shared_ptr).

Что дальше?

Основная проблема с std::auto_ptr заключается в том, что до C++11 в языке C++ просто не было механизма, позволяющего отличить «семантику копирования» от «семантики перемещения». Переопределение семантики копирования для реализации семантики перемещения привело к неопределённым результатам и непреднамеренным ошибкам. Например, вы можете написать item1 = item2 и вообще не знать, изменится ли item2 или нет!

По этой причине в C++11 понятие «перемещение» было формально определено, в следствии чего в С++ было добавлено ​​«семантику перемещения», чтобы должным образом отличать копирование от перемещения. Теперь, когда вы понимаете, чем семантика перемещения может быть полезной, мы рассмотрим её детальнее в оставшихся уроках этой главы.

В C++11 std::auto_ptr был заменён кучей других типов умных указателей:

Мы также рассмотрим два самых популярных из них: std::unique_ptr (который является прямой заменой std::auto_ptr) и std::shared_ptr.

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

На практике с C++, что такое RAII, что такое интеллектуальные указатели , как они реализованы в программе и каковы преимущества использования RAII с интеллектуальными указателями?

6 Ответов

Простой (и, возможно, чрезмерно используемый) пример RAII-это класс файлов. Без RAII код может выглядеть примерно так:

Другими словами, мы должны убедиться, что закрываем файл, как только закончим с ним. Это имеет два недостатка — во — первых, везде, где мы используем файл, нам придется вызывать File::close () — если мы забудем сделать это, мы будем держать файл дольше, чем нам нужно. Вторая проблема заключается в том, что если перед закрытием файла возникнет исключение?

Java решает вторую проблему с помощью предложения finally:

C++ решает обе проблемы с помощью RAII — то есть, закрывая файл в деструкторе файла. До тех пор, пока объект File будет уничтожен в нужное время (что в любом случае должно быть), закрытие файла заботится о нас. Итак, наш код теперь выглядит примерно так:

Причина, по которой это не может быть сделано в Java, заключается в том, что мы не можем гарантировать, когда объект будет уничтожен, поэтому не можем гарантировать, когда такой ресурс, как файл, будет освобожден.

На умные указатели-большую часть времени мы просто создаем объекты в стеке. Например (и украсть пример из другого ответа):

Это прекрасно работает — но что, если мы хотим вернуть str? Мы могли бы написать это:

Так что же в этом плохого? Ну, возвращаемый тип-std::string , так что это означает, что мы возвращаемся по значению. Это означает, что мы копируем str и фактически возвращаем копию. Это может быть дорого, и мы могли бы избежать затрат на его копирование. Поэтому мы могли бы прийти к идее возврата по ссылке или по указателю.

К сожалению, этот код не работает. Мы возвращаем указатель на str-но str был создан в стеке, поэтому мы будем удалены, как только выйдем из foo(). Другими словами, к тому времени, когда вызывающий получает указатель, он бесполезен (и, возможно, хуже, чем бесполезен, поскольку его использование может вызвать всевозможные фанковые ошибки)

Итак, каково же решение? Мы могли бы создать str в куче, используя new — таким образом, когда foo() будет завершен, str не будет уничтожен.

Конечно, это решение тоже не идеально. Причина в том, что мы создали str, но никогда не удаляем его. Это не может быть проблемой в очень маленькой программе, но в целом мы хотим убедиться, что мы удалим ее. Мы могли бы просто сказать, что вызывающий должен удалить объект, как только он закончит с ним. Недостатком является то, что вызывающий должен управлять памятью, что добавляет дополнительную сложность, и может получить ее неправильно, что приведет к утечке памяти, т. е. не удаляя объект, даже если он больше не требуется.

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

Теперь shared_ptr подсчитает количество ссылок на str. Например

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

Быстрое редактирование: как было отмечено в некоторых комментариях, этот пример не идеально подходит для (по крайней мере!) по двум причинам. Во-первых, из-за реализации строк копирование строки обычно обходится недорого. Во-вторых, из-за того, что известно как оптимизация именованных возвращаемых значений, возврат по значению может быть не слишком дорогим, так как компилятор может сделать некоторые хитрости, чтобы ускорить процесс.

Читать еще:  Выбор канала wifi роутера

Итак, давайте попробуем другой пример, используя наш класс File.

Допустим, мы хотим использовать файл в качестве журнала. Это означает, что мы хотим открыть наш файл в режиме только добавления:

Теперь давайте установим наш файл в качестве журнала для нескольких других объектов:

К сожалению, этот пример заканчивается ужасно — файл будет закрыт, как только закончится этот метод, что означает, что foo и bar теперь имеют недопустимый файл журнала. Мы могли бы построить файл в куче и передать указатель на файл как foo, так и bar:

Но тогда кто несет ответственность за удаление файла? Если ни один из них не удалит файл, то мы имеем как утечку памяти, так и утечку ресурсов. Мы не знаем, закончит ли фу или бар сначала с файлом, поэтому мы не можем ожидать, что они сами удалят файл. Например, если foo удаляет файл до того, как bar закончит с ним,то bar теперь имеет недопустимый указатель.

Так что, как вы уже догадались, мы могли бы использовать умные указатели, чтобы помочь нам.

Теперь никому не нужно беспокоиться об удалении файла — как только foo и bar закончат и больше не будут иметь никаких ссылок на файл (вероятно, из-за того, что foo и bar были уничтожены), файл будет автоматически удален.

RAII это странное название для простой, но удивительной концепции. Лучше это название, сферу связанными управления ресурсами (SBRM). Идея заключается в том, что часто вам приходится выделять ресурсы в начале блока, а освобождать их нужно на выходе из блока. Выход из блока может происходить при нормальном управлении потоком, выпрыгивании из него и даже при исключении. Чтобы охватить все эти случаи, код становится более сложным и избыточным.

Просто пример того, как это делается без SBRM:

Как вы видите, есть много способов, которыми мы можем получить PWN. Идея заключается в том, что мы инкапсулируем управление ресурсами в класс. Инициализация своего объекта приобретает ресурс («приобретение ресурса-это инициализация»). В момент выхода из блока (область действия блока) ресурс снова освобождается.

Это хорошо, если у вас есть собственные классы, которые не предназначены исключительно для распределения/освобождения ресурсов. Распределение было бы просто дополнительной заботой, чтобы выполнить свою работу. Но как только вы просто хотите выделить/освободить ресурсы, вышесказанное становится неуместным. Вы должны написать класс упаковки для каждого типа ресурсов, которые вы приобретаете. Чтобы облегчить это, интеллектуальные указатели позволяют автоматизировать этот процесс:

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

Существуют разные умные указатели для разных целей:

unique_ptr не

В отличие от auto_ptr, unique_ptr можно поместить в контейнер, потому что контейнеры смогут содержать не копируемые (но перемещаемые) типы, такие как потоки и unique_ptr.

scoped_ptr

shared_ptr

Как вы видите, сюжет-Источник (функция fx) общий, но у каждого есть отдельная запись, на которой мы устанавливаем цвет. Существует класс weak_ptr, который используется, когда код должен ссылаться на ресурс, принадлежащий интеллектуальному указателю, но не должен владеть ресурсом. Вместо того чтобы передавать необработанный указатель, вы должны затем создать weak_ptr. Он выдаст исключение, когда заметит, что вы пытаетесь получить доступ к ресурсу по пути доступа weak_ptr, даже если больше нет shared_ptr, владеющего ресурсом.

Предпосылки и причины просты, в принципе.

RAII-это парадигма проектирования, гарантирующая, что переменные обрабатывают всю необходимую инициализацию в своих конструкторах и всю необходимую очистку в своих деструкторах. Это сводит всю инициализацию и очистку к одному шагу.

C++ не требует RAII, но все чаще признается, что использование методов RAII приведет к созданию более надежного кода.

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

Связав всю инициализацию и очистку с этими механизмами, вы гарантируете, что C++ позаботится об этой работе и для вас.

Разговор о RAII в C++ обычно приводит к обсуждению интеллектуальных указателей, потому что указатели особенно хрупки, когда дело доходит до очистки. При управлении выделенной в куче памятью, полученной от malloc или new, обычно программист обязан освободить или удалить эту память до того, как указатель будет уничтожен. Интеллектуальные указатели будут использовать философию RAII, чтобы гарантировать, что выделенные объекты кучи уничтожаются каждый раз, когда уничтожается переменная указателя.

Умный указатель-это вариант RAII. RAII означает, что получение ресурсов является инициализацией. Умный указатель получает ресурс (память) перед использованием, а затем автоматически выбрасывает его в деструктор. Происходят две вещи:

  1. Мы выделяем память перед тем, как использовать ее, всегда, даже когда нам этого не хочется-трудно сделать по-другому с помощью умного указателя. Если этого не произошло, вы попытаетесь получить доступ к памяти NULL, что приведет к сбою (очень болезненному).
  2. Мы освобождаем память даже тогда, когда есть ошибка. Ни одно воспоминание не остается подвешенным.

Например, другой пример-сетевой сокет RAII. В этом деле:

  1. Мы открываем сетевую розетку перед тем,как использовать ее, всегда, даже когда нам не хочется . трудно сделать это по-другому с RAII. Если вы попытаетесь сделать это без RAII, вы можете открыть пустой сокет для, скажем, соединения MSN. Тогда сообщение, подобное «lets do it tonight», может не передаваться, пользователи не будут трахаться, и вы рискуете быть уволенным.
  2. Мы закрываем сетевой сокет даже тогда, когда есть ошибка. Ни один сокет не остается подвешенным, так как это может помешать ответному сообщению «sure ill be on bottom» вернуться к отправителю.

Теперь, как вы можете видеть, RAII-это очень полезный инструмент в большинстве случаев, поскольку он помогает людям трахаться.

C++ источники интеллектуальных указателей находятся в миллионах по всей сети, включая ответы выше меня.

У Boost есть несколько из них, включая те, что в Boost.Interprocess для общей памяти. Это значительно упрощает управление памятью, особенно в вызывающих головную боль ситуациях, например, когда у вас есть 5 процессов, разделяющих одну и ту же структуру данных: когда все закончили с куском памяти, вы хотите, чтобы он автоматически освободился &, а не сидел там, пытаясь выяснить, кто должен отвечать за вызов delete на куске памяти, чтобы вы не закончили с утечкой памяти или указателем, который по ошибке освобождается дважды и может повредить всю кучу.

Независимо от того, что произойдет, bar будет правильно удален, как только область действия функции foo() будет оставлена позади.

Внутренние реализации std::string часто используют указатели подсчета ссылок. Таким образом, внутренняя строка должна быть скопирована только тогда, когда одна из копий строк изменилась. Поэтому интеллектуальный указатель с подсчетом ссылок позволяет копировать что-либо только в случае необходимости.

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

Похожие вопросы:

В C++11 мы всегда должны использовать unique_ptr или shared_ptr вместо new / delete ? Как это с производительностью, умные указатели намного медленнее?

Я, и я думаю, что многие другие, имели большой успех, используя умные указатели, чтобы обернуть небезопасные операции памяти в C++, используя такие вещи, как RAII и так далее. Однако управление.

К моему большому стыду, у меня не было возможности использовать умные указатели в реальной разработке (супервизор считает это слишком ‘complex’ и пустой тратой времени). Тем не менее, я планировал.

Я работаю с gnump и есть функция, которая должна возвращать mpz_t . Поэтому я должен использовать необработанные указатели, чтобы вернуть значение. Я выделяю пространство с new для указателя и.

Я изучаю умные указатели ( std::auto_ptr ) и просто читаю здесь и здесь , что умные указатели ( std::auto_ptr ) не должны помещаться в контейнеры (т. е. std::vector ), потому что даже большинство.

Есть ли причины по-прежнему использовать необработанные указатели (для управляемых ресурсов) в C++11/14? Должны ли переменные-члены ресурса в классе храниться в своих собственных интеллектуальных.

Я хочу лучше понять, как реализовать идиому RAII с моими классами, на примере: какой рекомендуемый метод для обеспечения того, чтобы указатели были free()d правильно в моем классе? У меня есть.

Сегодня меня спросили об умных указателях в C++, и я не могу найти нигде полезной информации об этом.. Пожалуйста, может кто-нибудь сказать: Что такое умные указатели? Когда вам это нужно? У вас.

Я нахожу умные указатели гораздо более удобными, чем необработанные указатели. Так стоит ли всегда использовать умные указатели? ( Обратите внимание, что я из Java background и поэтому не очень.

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

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