До сих пор мы с вами инициализировали умные указатели в момент их создания, и после этого их не меняли. Однако умные указатели можно присваивать, и периодически это бывает полезно. Давайте посмотрим, как это делается. Вот здесь у нас открыт пример, который мы использовали для демонстрации возможностей shared_ptr. Здесь мы создаем один Actor. Для того чтобы показать, как умные указатели присваиваются, нам понадобится парочка акторов. А поэтому давайте, чтобы наши акторы обрели некоторую индивидуальность, их можно было отличать друг от друга, дадим актору имя и будем передавать его в конструкторе. Так, так. И сделаем, чтобы у нас, когда актор что-то говорит, чтобы он выводил информацию о том, кто это делает, то есть здесь будем вводить имя. Хорошо. Теперь давайте изменим нашу функцию main, продемонстрируем возможности присваивания на примере unique_ptr (для shared_ptr мы поговорим чуть-чуть позже), то есть здесь мы делаем make_unique<Actor>. Давайте скажем, что первый актор у нас называется Алиса. Создадим второй актор и назовем его, скажем, Борис. После этого, как мы обычно это делали, вызовем для акторов нашу функцию run. run для первого и run для второго. Давайте соберем и запустим эту программу. Так. Это он ругается на то, что не умеет выводить строки. Давайте подключим заголовочный файл string, чтобы он научился их выводить в выходной поток. Отлично. Программка собралась, давайте ее запустим. Мы видим достаточно ожидаемый вывод. У нас сначала создалась Алиса, потом создался Борис, они оба выполнили некоторую работу и после этого оба дружно умерли в обратном порядке. Все вполне ожидаемо. Хорошо. Давайте теперь выполним присваивание, то есть напишем, что ptr1 = move (ptr2). Написать просто присваивание для unique_ptr мы не можем, потому что просто присваивание было бы копирующим, а unique_ptr мы копировать не можем. Поэтому здесь мы выполним перемещение. Напишем таким образом: ptr1 = move (ptr2). И теперь, прежде чем мы запустим программу, вопрос к вам: как вы думаете, что напечатают вот эти три строчки? Для того чтобы нам с вами было лучше видно, что же эти строчки напечатают, давайте сделаем вот такую отбивку. Чтобы в выходе их явно было видно. Соберем программу в таком виде и запустим. Итак, что же мы видим? Что первым делом после присваивания у нас удаляется Алиса. Как же так произошло? Смотрите. У нас ptr1 указывал на Алису, а потом ptr1 присваивается тому, на что указывал ptr2. То есть получается, что на Алису в этот момент уже больше никто не указывает. Раз на нее больше никто не указывает, но она при этом управлялась умным указателем, она соответственно должна быть удалена, иначе бы она утекла. Соответственно, в этот момент она удаляется. Смотрим дальше на вывод. У нас какую-то работу выполняет Борис, и после этого вызывается run с нулевым актором. Ну действительно, ptr1 у нас теперь указывает туда, куда указывал ptr2, то есть на Бориса, а из ptr2 у нас было выполнено перемещение. То есть в нем у нас ничего не осталось. И поэтому при этом втором вызове run у нас выводится сообщение, что актора там нет. Все вполне логично. Давайте теперь попробуем сформулировать точное правило, в какой момент у нас удаляются объекты, которые управляются умными указателями. До этого момента мы как бы говорили, что умные указатели удаляют объект в своем деструкторе. И это правда, но не вся. Дело в том, что умные указатели могут не удалить объект в своем деструкторе, например, у нас есть один shared_ptr, он указывает на объект, он удаляется, но при этом другой shared_ptr продолжает указывать на тот же самый объект. Как мы прекрасно знаем, в этом случае объект не будет удален. Кроме того, умные указатели могут удалить объект не в деструкторе, как мы это с вами только что видели, умный указатель удалил объект в момент присваивания, то есть после присваивания у нас Алиса была удалена. Наиболее точное правило будет звучать подобным образом: динамический объект удаляется, когда им перестают владеть умные указатели. При этом перестать владеть объектом умные указатели могут в несколько моментов. Во-первых, при разрушении, с этого мы собственно и начали. Во-вторых, при перемещении из умного указателя, это мы тоже подробно рассмотрели. И при присваивании умного указателя чему-то. Это мы с вами только что видели на примере присваивания unique_ptr. Давайте теперь подробнее поговорим именно про присваивание, потому что в C++ их существует несколько разных видов. Для начала перемещающее присваивание — ровно то, что мы сейчас с вами делали в программе. У нас ptr1 указывает на Алису, ptr2 указывает на Бориса. После того как мы выполняем перемещающее присваивание, у нас ptr1 начинает указывать туда, куда указывал ptr2, то есть на Бориса. Вот так. При этом ptr2 у нас не указывает больше никуда, потому что мы из него переместили, а на Алису в этот момент никто не указывает. То есть в соответствии с нашей формулировкой умные указатели прекратили владение Алисой. Следовательно, в этот момент она должна быть удалена, раз на нее больше никто не указывает. Вот она и удаляется. Собственно, то, что мы с вами и видели. Это перемещающее присваивание, и оно будет одинаково работать и для unique_ptr, и для shared_ptr. Давайте теперь посмотрим на копирующее присваивание. Копирующее присваивание, очевидно, применимо только к shared_ptr, потому что у них ptr копировать нельзя. Пусть у нас есть вот такая раскладка. Опять же, у нас Алиса и Борис, и три shared_ptr. Первый указывает на Алису, а остальные два указывают на Бориса. Мы присваиваем ptr2 = ptr1. После этого присваивания у нас ptr2 начинает указывать вместо Бориса на Алису, потому что ровно туда указывал ptr1. Но при этом у нас на все объекты продолжают указывать какие-то умные указатели, то есть больше ничего в этот момент не происходит. Однако дальше давайте выполним еще одно присваивание. Теперь ptr3 присваивается ptr1, и ptr3 тоже начинает указывать на Алису вместо Бориса. Теперь Борисом больше не владеет ни один умный указатель, следовательно, в этот момент он должен быть удален, что и происходит. Здесь Борис и удаляется. Это копирующее присваивание. И теперь давайте посмотрим на еще один интересный вид присваивания, это присваивание nullptr. Оно, опять же, применимо и к unique_ptr, и к shared_ptr. Когда мы присваиваем умный указатель nullptr, он просто становится простым и прекращает владение объектом, на который он указывал. И если на объект больше никто не указывает, следовательно, объект удаляется. Как в данном случае. Подобное присваивание очень полезно, если нам нужно освободить владение объектом до того, как умный указатель прекратил свое существование. То есть здесь ptr1 у нас продолжает существовать, но больше уже никуда не указывает. Итак, давайте подытожим. Мы с вами сформулировали, что динамический объект удаляется, когда им перестают владеть умные указатели. Или, что то же самое, когда на него перестают указывать умные указатели. И мы рассмотрели несколько видов присваивания умных указателей: перемещающее присваивание, копирующее присваивание и присваивание nullptr. При этом все присваивания, кроме копирующего, они применимы как к unique_ptr, так и к shared_ptr, а копирующее присваивание, очевидно, применимо только к shared_ptr. А в следующем видео мы с вами поговорим о такой важной теме, как shared_ptr в многопоточной среде.