Теперь, когда мы с вами познакомились с возможностями shared_ptr, а конкретно с главной его возможностью, что его в отличие от unique_ptr можно копировать, мы готовы изменить решение задачи для дерева выражений таким образом, чтобы удовлетворить наше новое требование. Вот здесь у нас авторское решение с помощью unique_ptr'ов. Оно у нас собрано. Давайте его запустим и видим, что оно у нас печатает e1, затем печатает e2, снова печатает e1, но при этом в e1 у нас ничего не осталось, поэтому выводится строка, что Null expression, потому что из e1 мы переместили. Хорошо, давайте напишем решение с помощью shared_ptr'ов. Начнем с того, что все умные указатели, которые здесь используются, переделаем с unique_ptr на shared_ptr. Вот здесь был unique_ptr, пишем shared_ptr. Так. И дальше у нас есть функции, которые создают новые объекты. Они у нас вызывают make_unique. Здесь соответственно тоже вместо make_unique нам нужно использовать make_shared, хорошо, здесь напишем make_shared. И как мы помним, shared_ptr умеет делать все то же самое, что и unique_ptr. Поэтому такую программу мы уже можем собрать, вот она у нас собралась. Запустить. Она у нас будет выводить все то же самое, но при этом она работает уже на shared_ptr'ах. И теперь вопрос к вам: как нужно изменить функцию main, чтобы повторное обращение функции Print для e1 у нас выводило не строчку о том, что там нулевой указатель, а чтобы оно точно так же снова выводило содержимое дерева e1? Ну, почему у нас вообще там выводилось сообщение о том, что у нас нулевое выражение? Потому что мы переместили из указателя e1. В случае с unique_ptr выхода у нас не было, нам нужно было перемещать. Но у нас же теперь shared_ptr, соответственно нам нужно вместо перемещения использовать копирование. И ответ очень простой: нам нужно удалить функцию move. То есть теперь вместо перемещения у нас будет использоваться копирование. То есть вот здесь у нас мы просто пишем сумму e1. Отлично, давайте посмотрим, как работает такое исправление. Компилируем, запускаем нашу программу и видим, что все отлично работает. То есть мы снова обратились по указателю e1 и напечатали исходное дерево. Хорошо. Давайте теперь проверим, что у нас действительно при удалении указателя e2 то наше поддерево, на которое указывает e1, останется без изменений. То есть, что shared_ptr в данном случае лучше, чем сырой указатель. Так. Давайте заключим создание вот этого e2 и его распечатывания в блок, и как мы знаем, у нас e2 будет уничтожен по выходу из блока. Проверим, что у нас e1 будет продолжать работать. Соберем такую программу. Так, и запустим ее, и видим, что у нас вывод корректный, то есть действительно у нас то поддерево, на которое указывает e1, оно не пострадало, несмотря на то, что e2 удалилось. То есть все работает нормально. Но вот уберем этот блок. А теперь смотрите, на самом деле мы можем делать даже более интересные вещи здесь. Например, давайте заведем такой ExpressionPtr e3, который будет равен сумме e1 и e2, и напечатаем уже вот такой e3. Так, e3.get(). И теперь вопрос к вам: как вы думаете, что напечатает вот эта последняя строчка Print(e3)? Давайте соберем программу и посмотрим, что же она на самом деле напечатает. Так, мы запускаем и видим, что она работает на самом деле абсолютно логично. Что здесь написано? Сумма e1 и e2. e1 мы знаем, вот у нас первая строчка, e2 мы тоже знаем, вот вторая строчка. Значит, e1 + e2 — это просто их сумма. А то, что на самом деле там дерево e1 входит в это выражение дважды, это сути дела не меняет, все и так отлично работает, как раз потому что shared_ptr отлично умеет указывать на один и тот же узел из нескольких мест. Хорошо, давайте теперь рассмотрим чуть подробнее, как же у нас эти узлы расположены в памяти и друг на друга ссылаются. Вот так у нас теперь создается дерево выражений для e2, отличие в том, что мы убрали функцию move. То есть теперь указатель e1 мы не перемещаем, а копируем. Здесь вызывается Value для 5, возвращает временный в данном случае уже shared_ptr на новый объект, который хранит пятерку. И дальше вот этот временный shared_ptr перемещается внутри функции Sum, а e1 не перемещается, e1 копируется, потому что временные объекты всегда по умолчанию перемещаются. Как вы знаете, так работает семантика перемещения. А вот e1, мы ему не сказали в явном виде перемещаться, значит, он у нас будет копироваться. И Sum создает нам новый узел SumExpr, который в себе сохраняет указатели на вот эти два узла на пятерку умножения и сохраняет результат в указатель e2. Теперь давайте посмотрим, а как у нас происходит удаление e2. Для этого мы немного раскроем этот узел SumExpr, чтобы заглянуть внутрь, что у него там происходит. И посмотрим, собственно, по шагам, как будет удаляться e2. Итак, начинается удаление e2. Это shared_ptr, он указывает на узел SumExpr, начинает удалять SumExpr. SumExpr при своем удалении начинает удалять свои поля, начинает с поля right. Видит, что ссылается на 5, значит, удаляет 5. Дальше он начинает удалять поле left, поле left ссылается на узел умножения. И вот тут самое интересное: shared_ptr видит, что на этот узел умножения кроме него ссылается еще другой shared_ptr — e1, и поэтому он не будет удалять этот узел. Он просто удалится сам, а узел трогать не будет. На этом удаление SumExpr заканчивается, и удаление e2 тоже заканчивается. А поддерево e1 у нас остается без изменений. Теперь давайте посмотрим на вот этот наш пример, когда мы сделали сумму выражений e1 и e2. Очень интересно: создается новый узел e3, при этом и e1, и e2 копируются в функцию Sum, она создает новый узел e3, который ссылается вот на уже существующие узлы, которые адресуются указателями e1 и e2. И все прекрасно работает, никаких проблем на самом деле с этим нет. Если на unique_ptr'ах у нас было дерево, то на shared_ptr'ах мы смогли сделать направленный ациклический граф. При этом, опять же, если в случае с unique_ptr'ами у нас получался рекурсивный обход, абсолютно бесплатный, компилятор сам его делал, здесь точно так же: то, что это граф, и то, что в нем нет циклов, нам гарантирует просто семантика работы с shared_ptr'ами. Вот таким по сути тривиальным изменением нашей реализации на unique_ptr'ах, просто заменив их на shared_ptr'ы, мы смогли реализовать новое требование. А в следующих видео мы с вами более подробно поговорим уже о внутреннем устройстве этих умных указателей.