[БЕЗ_ЗВУКА] Теперь давайте вернемся к нашей программе и более внимательно посмотрим на функцию main. Мы видим, что для каждого объявленного объекта мы вызываем отдельно функцию MakeSound, то есть в нашем случае мы видим, что у нас есть дупликация кода и четыре очень похожих друг на друга вызовов. Это может быть неудобно в том случае, если мы, например, не знаем заранее количество объектов, для которых мы бы хотели эту функцию вызвать. Например, если бы мы эти объекты пытались создать с учетом ввода от пользователя. То есть, например, пользователь бы вводил тип объекта и какую-то дополнительную информацию. Нам бы тогда потребовалось сохранять все эти разные объекты и для каждого из них потом вызывать функцию MakeSound. Как бы мы могли это сделать? Конечно же, на ум сразу приходят контейнеры, которые мы уже хорошо знаем. Мы бы могли сложить все наши объекты в контейнер, добавлять их по мере надобности и с использованием цикла for для каждого объекта из контейнера вызвать функцию MakeSound. Давайте попробуем написать код, который бы это делал. Мы не будем писать ввод от пользователя, мы попробуем написать контейнер и инициализировать его ограниченным количеством объектов. Как бы это могло выглядеть? Допустим, у нас есть вектор animals. Я намеренно пропустил тип объектов, которые мы будем хранить в векторе — мы к этому вернемся чуть позже. Как мы обычно делаем? У нас есть вектор, мы берем каждый объект из этого вектора и что-то с ним делаем. Давайте удалим код явного вызова функции MakeSound. И удалим сами объекты. Оставим только работу с вектором. Итак, какой же тип для объектов нам поместить в вектор? У нас все типы разные, то есть объекты могут быть как кошкой, собакой, так и попугаем. Как же нам их все объединить? Как мы хорошо знаем из лекции про наследование, объединить мы их можем, например, с помощью ссылки на базовый класс. Давайте так и попробуем. Попробуем создать вектор ссылок. Давайте запустим сборку. Как мы видим, компилятор выдал нам огромное количество ошибок, что свидетельствует о том, что вектор из ссылок у нас создать не получится. Мы не будем вдаваться в детали и подробно рассматривать эти ошибки, мы примем как есть, то есть вектор ссылок у нас недоступен. Как же нам тогда по-другому можно объединить наши различные объекты разных классов? Для этого нам поможет другой тип, который есть в стандартной библиотеке C++. Давайте мы его рассмотрим. Тип называется shared_ptr. Записывается он следующим образом: shared_ptr. Так же, как и у контейнеров, у него в угловых скобочках нужно указать другой тип, потому что, по сути, сам shared_ptr является не более, чем оберткой над каким-то другим объектом. И вот тип именно этого объекта мы можем указать в угловых скобочках. В нашем случае мы можем указать тип animal. Давайте немного отвлечемся от виртуальных методов и рассмотрим более подробно тип shared_ptr. Во-первых, у этого типа есть несколько полезных свойств. Мы рассмотрим только те, которые нам нужны сейчас. Первое и самое полезное свойство, которое нам поможет решить нашу задачу — это то, что мы в качестве типом объекта, который оборачивается в shared_ptr можем указать просто animal, не ссылку на него. И при этом shared_ptr будет вести себя как ссылка. То есть мы сможем в него положить объекты производных классов и безболезненно обращаться к интерфейсу базового класса. То есть примерно все то же самое, как и при работе с ссылкой. Это одно полезное свойство. Второе полезное свойство — что объекты типа shared_ptr имеют очень похожий интерфейс на итератора. То есть с ними довольно просто работать. Давайте посмотрим, как же, во-первых, мы можем обернуть какой-то объект типа Cat, Dog или Parrot в наш shared_ptr. Делается это следующим образом. Есть специальная функция, которая называется make_shared. У нее в угловых скобках необходимо указать реальный тип объекта, который мы хотим создать. Давайте создадим объект типа Cat. Затем идут круглые скобки, то есть это просто вызов функции. Давайте мы пока закомментируем наш код с вектором, которого не существует и посмотрим, как работает shared_ptr. Как я уже сказал, он очень похож на итератор с точки зрения интерфейса, то есть синтаксиса. Как же нам, например, вызвать метод Sound? Мы записываем название объекта, затем мы указываем стрелочку. И указываем метод, который мы хотим вызвать. Давайте посмотрим, что у нас получится. Компилятор говорит нам о том, что он не знает метод shared_ptr — все верно. Мы забыли указать заголовок, в котором этот тип объявлен. Заголовок называется memory. Давайте вернемся к нашему shared_ptr и попробуем собрать еще раз. Запускаем программу. И мы видим, что все работает, то есть у нас обертка над типом animal, создаем мы объект типа Cat, вызываем метод Sound, и видим, что вызвался метод Sound из класса Cat. То есть такое поведение нам более чем устраивает. Давайте теперь попробуем решить исходную задачу — вызов функции MakeSound для объектов из вектора. Давайте теперь в качестве типа внутри вектора укажем shared_ptr на animal. [ШУМ] Раскомментируем ранее закомментированный код. [ШУМ] Так же, как и итераторы, shared_ptr не обязательно передавать по ссылке. То есть мы можем убрать ссылку, убрать константность. [ШУМ] И мы видим, что компилятору по-прежнему что-то не нравится. Он говорит нам о том, что не может преобразовать в ссылку на класс animal объект типа shared_ptr. Все верно. В этом случае нам поможет синтаксис, опять же схожий с синтаксисом итераторов — мы должны поставить звездочку перед объектом типа shared_ptr, чтобы получить тот объект, который в нем обернут. Давайте попробуем собрать и запустить. Как мы видим, у нас по-прежнему выводится слово «мяу» — это вызов функции Sound для объекта a. Давайте мы его удалим. И инициализируем наш вектор. Сейчас он пустой, поэтому ничего кроме единственной строчки мы в выводе программы не видим. Давайте попробуем его инициализировать различными объектами. [ШУМ] [ШУМ] Мы видим, что компилятор при создании объекта «Попугай» выдает нам ошибку. В чем же она заключается? Она заключается в том, что в конструкторе объекта «Попугай» обязательно необходимо передать строчку с его именем. Но мы ведь явно не вызываем конструктор — мы вызываем функцию make_shared. Каким же образом нам правильно инициализировать наш объект? Сделать это довольно просто. В качестве аргументов функция make_shared как раз принимает тот набор объектов, значений, строк, чисел, которые она затем передает в конструктор этого объекта. Давайте попробуем. [ШУМ] [ШУМ] Программа собралась, давайте ее запустим. И, как мы видим, у нас все объявленные нами объекты — правильно для них вызвалась функция Sound, и в консоли мы видим результаты ее работы. То есть с помощью shared_ptr мы можем помещать различные типа объектов производных классов в контейнеры и обходить их с помощью циклом так же, как и обычные типы.