Умный указатель c что это

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

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

Благодаря наличию исключений, язык 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 запрещено копировать:

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

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

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

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

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

#include
#include
#include
#include

template typename T >
using auto_cleanup = std :: unique_ptr T,std :: function void ( T * ) >> ;

#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__; >);

defer ( std :: cout «Bye #1» std :: endl ) ;
defer ( std :: cout «Bye #2» std :: endl ) ;

Заметьте, что в макросе defer нам пришлось передать в unique_ptr фиктивный указатель. Если бы мы передали nullptr, custom deleter не был бы вызван.

shared_ptr и weak_ptr

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

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

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

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

class SomeClass <
public :
void sayHello ( ) <
std :: cout «Hello!» std :: endl ;
>

SomeClass ( ) <
std :: cout «

int main ( ) <
std :: weak_ptr SomeClass > wptr ;

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

SomeClass
lock() failed

Можно думать о weak_ptr как об указателе, позволяющим получить временное владение объектом. Само собой разумеется, если все постоянные указатели на объект перестанут существовать, и останутся только временные, полученные при помощи метода lock() класса weak_ptr, объект продолжит свое существование. Он будет уничтожен только тогда, когда на объект не останется вообще никаких указателей.

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

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

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

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

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

virtual void sayHello ( ) <
std :: cout «I’m Base #» num std :: endl ;
>

Base #» num std :: endl ;
>

protected :
int num ;
> ;

class Derived : public Base <
public :
Derived ( int num ) : Base ( num )

virtual void sayHello ( ) <
std :: cout «I’m Derived #» num std :: endl ;
>

Derived ( ) <
std :: cout «

Derived #» num std :: endl ;
>
> ;

void testUnique ( ) <
std :: cout «=== testUnique begin ===» std :: endl ;

std :: cout «=== testUnique end ===» std :: endl ;
>

void testShared ( ) <
std :: cout «=== testShared begin ===» std :: endl ;

std :: cout «=== testShared end ===» std :: endl ;
>

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

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

Заключение

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

Источник

Smart pointers для начинающих

Эта небольшая статья в первую очередь предназначена для начинающих C++ программистов, которые либо слышали об умных указателях, но боялись их применять, либо они устали следить за new-delete.

UPD: Статья писалась, когда C++11 еще не был так популярен.

Введение

Существует техника управления ресурсами посредством локальных объектов, называемая RAII. То есть, при получении какого-либо ресурса, его инициализируют в конструкторе, а, поработав с ним в функции, корректно освобождают в деструкторе. Ресурсом может быть что угодно, к примеру файл, сетевое соединение, а в нашем случае блок памяти. Вот простейший пример:

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

В принципе, никакой разницы, но представим себе такой код:

Придется в каждой ветке выхода из функции писать delete [], либо вызывать какие-либо дополнительные функции деинициализации. А если выделений памяти много, либо они происходят в разных частях функции? Уследить за всем этим будет все сложнее и сложнее. Подобная ситуация возникает, если мы в середине функции бросаем исключение: гарантируется, что объекты на стеке будут уничтожены, но с кучей проблема остается открытой.
Ок, будем использовать RAII, в конструкторах инициализировать память, в деструкторе освобождать. И пусть поля нашего класса будут указателями на участки динамической памяти:

Простейший smart pointer

boost::scoped_ptr

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

Оно и понятно, если бы было разрешено присваивание, то и p1 и p2 будут указывать на одну и ту же область памяти. А по выходу из функции оба удалятся. Что будет? Никто не знает. Соответственно, этот пойнтер нельзя передавать и в функции.
Тогда зачем он нужен? Советую применять его как указатель-обертка для каких-либо данных, которые выделяются динамически в начале функции и удаляются в конце, чтобы избавить себя от головной боли по поводу корректной очистки ресурсов.
Подробное описание здесь.

std::auto_ptr

Чуть-чуть улучшенный вариант предыдущего, к тому же он есть в стандартной библиотеке (хотя в C++11 вроде как deprecated). У него есть оператор присваивания и конструктор-копировщик, но работают они несколько необычно.
Поясняю:

Теперь при присваивании в p2 будет лежать указатель на MyObject (который мы создавали для p1), а в p1 не будет ничего. То есть p1 теперь обнулен. Это так называемая семантика перемещения. Кстати, оператор копирования поступает таким же образом.
Зачем это нужно? Ну например у вас есть функция, которая должна создавать какой-то объект:

Это означает, что функция создает новый объект типа MyObject и отдает его вам в распоряжение. Понятней станет, если эта функция сама является членом класса (допустим Factory): вы уверены, что этот класс (Factory) не хранит в себе еще один указатель на новый объект. Объект ваш и указатель на него один.
В силу такой необычной семантики auto_ptr нельзя использовать в контейнерах STL. Но у нас есть shared_ptr.

std::shared_ptr (С++11)

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

Теперь и p2 и p1 указывают на один объект, а счетчик ссылок равен 2, По выходу из скоупа счетчик обнуляется, и объект уничтожается. Мы можем передавать этот указатель в функцию:

Заметьте, если вы передаете указатель по ссылке, то счетчик не будет увеличен. Вы должны быть уверены, что объект MyObject будет жив, пока будет выполняться функция test.

Итак, smart pointers это хорошо, но есть и минусы.
Во-первых это небольшой оверхед, но я думаю у вас найдется несколько тактов процессора ради такого удобства.
Во-вторых это boiler-plate, например

Это частично можно решить при помощи дефайнов, допустим:

Либо при помощи typedef.
В-третьих, существует проблема циклических ссылок. Рассматривать ее здесь не буду, чтобы не увеличивать статью. Так же остались нерассмотренными boost::weak_ptr, boost::intrusive_ptr и указатели для массивов.
Кстати, smart pointers достаточно хорошо описаны у Джеффа Элджера в книге «С++ for real programmers».

Источник

Что такое умный указатель, и когда я должен его использовать?

Что такое умный указатель и когда я должен его использовать?

ОТВЕТЫ

Ответ 1

UPDATE

СТАРЫЙ ОТВЕТ

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

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

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

Обратите внимание, что экземпляры std::unique_ptr не могут быть скопированы. Это предотвращает многократное удаление указателя (неправильно). Однако вы можете передавать ссылки на него другим вызываемым функциям.

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

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

Ответ 2

Вот простой ответ для современных дней C++:

Ответ 3

Одним из простых типов интеллектуальных указателей является std::auto_ptr (глава 20.4.5 стандарта C++), который позволяет автоматически освобождать память, когда она выходит из области видимости, и является более надежным, чем использование простого указателя при возникновении исключений, хотя и менее гибкий.

Другим удобным типом является boost::shared_ptr который реализует подсчет ссылок и автоматически освобождает память, когда не осталось ссылок на объект. Это помогает избежать утечек памяти и прост в использовании для реализации RAII.

Тема подробно рассмотрена в книге Дэвида Вандевурда «Николас М. Хосуттис», глава 20 » Шаблоны C++: Полное руководство», глава 20 «Умные указатели». Некоторые темы:

Ответ 4

Ответ 5

Умный указатель похож на обычный (типизированный) указатель, например «char *», за исключением случаев, когда сам указатель выходит из области видимости, и то, на что он указывает, также удаляется. Вы можете использовать его так же, как и обычный указатель, используя «- > «, но не если вам нужен фактический указатель на данные. Для этого вы можете использовать «& * ptr».

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

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

Вы можете not использовать интеллектуальный указатель, если:

Ответ 6

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

shared_ptr очень универсален и имеет дело с большим разнообразием сценариев удаления, включая случаи, когда объекты должны быть «переданы через границы DLL» (общий случай кошмара, если между вашим кодом и DLL используются разные libc ).

Ответ 7

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

Например, Boost предоставляет следующие реализации интеллектуальных указателей:

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

Ответ 8

Пример:

Этот класс реализует интеллектуальный указатель на объект типа X. Сам объект находится в куче. Вот как его использовать:

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

Ответ 9

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

Ответ 10

1) Необработанные указатели:

Они хранят адрес памяти в месте в памяти. Следует использовать с осторожностью, так как программы становятся сложными для отслеживания.

Указатели с данными или адресом const

Указатель const для типа данных T. Это означает, что вы не можете переместить указатель, но вы можете изменить значение, на которое указывает указатель. т.е. *ptr2 = 19 будет работать, но ptr2++ ; ptr2— и т.д. не будет работать. Прочитать назад: const указатель на тип T

Указатель const для типа данных const. Это означает, что вы не можете либо переместить указатель, либо не можете изменить указатель типа данных как указатель. т.е. ptr3— ; ptr3++ ; *ptr3 = 19; не будет работать

3) Интеллектуальные указатели:

Общий указатель:

Реализовано с использованием подсчета ссылок, чтобы отслеживать, сколько «вещей» указывает на объект, на который указывает указатель. Когда этот счетчик переходит в 0, объект автоматически удаляется, т.е. Объект objected удаляется, когда весь share_ptr, указывающий на объект, выходит из области видимости. Это избавляет от головной боли, связанной с необходимостью удаления объектов, которые вы выделили с помощью нового.

Слабый указатель: Помогает справиться с циклической ссылкой, которая возникает при использовании Shared Pointer Если у вас есть два объекта, на которые указывают два общих указателя, и есть внутренний общий указатель, указывающий на общий указатель друг друга, тогда будет циклическая ссылка, и объект не будет удален, если общие указатели выйдут из области видимости. Чтобы решить эту проблему, измените внутренний член с shared_ptr на weak_ptr. Примечание. Чтобы получить доступ к элементу, на который указывает слабый указатель, используйте lock(), это возвращает weak_ptr.

Уникальный указатель: Легкий интеллектуальный указатель с исключительной властью. Используйте, когда указатель указывает на уникальные объекты, не разделяя объекты между указателями.

Чтобы изменить объект, на который указывает уникальный ptr, используйте семантику перемещения

Ссылки: Они могут быть, по существу, как константные указатели, то есть указатель, который является const и не может быть перемещен с лучшим синтаксисом.

Ссылка: https://www.youtube.com/channel/UCEOGtxYTB6vo6MQ-WQ9W_nQ Спасибо Андре за то, что он указал на этот вопрос.

Ответ 11

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

Ответ 12

Вы можете очень хорошо использовать этот указатель так же, как любое распределение работает в Java. В java Garbage Collector делает трюк, в то время как в Smart Pointers трюк выполняется Destructors.

Ответ 13

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

Среди других вещей (хорошо объясняемых в других ответах) использование умного указателя является возможным решением Как мы используем абстрактный класс как возвращаемый тип функции?, который был отмечен как дубликат этого вопроса. Тем не менее, первый вопрос, задающий вопрос об искушении указать абстрактный (или фактически, какой-либо) базовый класс как возвращаемый тип в С++, «что вы на самом деле имеете в виду?». Существует хорошее обсуждение (с дополнительными ссылками) идиоматического объектно-ориентированного программирования в С++ (и как это отличается от других языков) в документации указатель форсирования контейнерная библиотека. Таким образом, на С++ вы должны думать о собственности. Какие интеллектуальные указатели вам помогут, но не единственное решение или всегда полное решение (они не дают вам полиморфной копии) и не всегда являются решением, которое вы хотите разоблачить в своем интерфейсе (а функция return звучит ужасно как интерфейс). Например, может быть достаточно вернуть ссылку. Но во всех этих случаях (умный указатель, контейнер указателя или просто возврат ссылки) вы изменили возврат из значения в некоторую форму ссылки. Если вам действительно нужна копия, вам может потребоваться добавить более подробную «илидиому» шаблона или перейти за идиоматический (или иначе) ООП на С++ к более универсальному полиморфизму, используя библиотеки, такие как Adobe Poly или Boost.TypeErasure.

Ответ 14

Я хотел бы добавить еще один момент к вышеуказанному вопросу, умный указатель std:: shared_ptr не имеет оператора индекса и не поддерживает арифметику ponter, мы можем использовать get() для получения встроенного указателя.

Источник

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

Обновл. 15 Сен 2021 |

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

Проблема

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

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

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

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

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

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

Item acquired
Item destroyed
Item destroyed

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

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

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

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

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

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

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

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

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

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

Item acquired
item1 is not null
item2 is null
Ownership transferred
item1 is null
item2 is not null
Item destroyed

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.

Поделиться в социальных сетях:

Глава №14. Итоговый тест

Комментариев: 8

Наверное потому, что этот нюанс подробно рассматривался в предыдущих уроках?

Интересно, а можно ли помимо самого указателя, в умном указателе держать счетчик (количество держателей указателя), который бы при создании, присваивании увеличивалсяч на 1, а при работе деструктора уменьшался на 1. В, общем так, как реализовывается подсчет ссылок в COM объектах. И когда доходит до 0 — должно происходить освобождение памяти.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *