[БЕЗ_ЗВУКА] На текущий момент мы с вами уже довольно подробно познакомились с управлением памятью в C++. Настало время несколько структурировать наши знания. Давайте посмотрим, какие вообще в C++ бывают виды объектов с точки зрения времени их жизни. У нас бывают автоматические объекты, временем жизни автоматических объектов управляет компилятор. К автоматическим объектам относятся локальные переменные, глобальные переменные и члены классов. Также у нас бывают динамические объекты. Временем жизни динамических объектов управляет программист. Мы уже знаем, что динамические объекты создаются с помощью ключевого слова New, которое вызывается неявно в недрах функций make_unique и make_shared. А удаляются они с помощью ключевого слова delete, которое опять же вызывается неявно в деструкторах умных указателей. Также в C++ существует понятие владения. Считается, что некоторая сущность владеет объектом, если она отвечает за его удаление. Давайте посмотрим, кто владеет разными видами объектов. Для автоматических объектов: если говорить о локальных переменных, то локальными переменами владеет окружающий их блок кода. Действительно, когда поток управления программы выходит из блока кода, мы знаем, что уничтожаются все локальные переменные, которые были объявлены в этом блоке. Глобальные переменные: можно считать, что ими владеет сама программа. Мы знаем, что когда программа завершается, уже после завершения функции main, происходит удаление всех глобальных переменных. А членами класса владеет сам объект класса. Действительно, мы никогда не задумываемся о том, когда нам нужно удалять члены класса. Мы знаем, что в любой момент, как бы это ни произошло, когда будет уничтожен сам объект класса, он позаботится о том, чтобы в своем деструкторе удалить свои члены. И с автоматическим объектами: эти правила, которые мы здесь перечислили, они очень четкие, и им следует компилятор. Они всегда работают ровно так. С динамическими объектами ситуация несколько интереснее. Как вы думаете, кто владеет динамическими объектами? Как вы можете догадаться по названию данного блока, динамическими объектами владеют умные указатели. При этом unique_ptr обеспечивает уникальное, эксклюзивное владение объектами. На один объект может указывать только один unique_ptr, и один unique_ptr может указывать только на один объект. Shared_ptr — это разделяемое, или совместное, владение. На один и тот же объект может указывать несколько shared_ptr. А вот сырой указатель не владеет объектом. Считается, что если у нас есть сырой указатель, мы его получили каким-то образом, то мы никаким образом не отвечаем за время жизни этого объекта, на который он указывает. И вот тут ситуация достаточно интересна с динамическими объектами в том плане, что вот это то, что мы сейчас перечислили, это соглашение. Это не совсем правила. И этим соглашениям должен следовать программист. При этом программист, вообще говоря, может их нарушить. Давайте посмотрим на эти соглашения в действии на конкретном примере кода. Например, у нас есть функция CreateAnimal. Посмотрим на ее прототип. Она принимает строчку и возвращает unique_ptr. Нам даже не нужно заглядывать внутрь этой функции, чтобы понять, что эта функция создает объект и возвращает нам его во владение. Потому что она возвращает нам unique_ptr. Теперь мы у себя сохраняем uniqie_ptr, мы как-то им пользуемся, и владение находится на нашей стороне. Эта функция следует данным соглашениям. Следующий пример: у нас есть объект «фигура». В этом объекте у нас есть поле «текстура», которое сохранено как shared_ptr. И этот shared_ptr нам передается в конструкторе То есть из сигнатуры конструктора мы можем понять, что новый объект фигуры, который создан с помощью этого конструктора, он будет находиться в совместном владении этой текстурой. Этот пример также следует нашим соглашениям. Следующий пример с использованием сырого указателя. Наша функция Print, которая принимает сырой указатель на объект, как-то пользуется этим объектом, здесь вызывает ToString, вызывает Evaluate, но при этом она, разумеется, не удаляет этот объект и ничего не делает с ним, что относилось бы к времени жизни этого объекта. То есть указатель, который она получила, — не владеющий. Давайте подведем небольшой итог. Существуют соглашения по владению динамическими объектами. Этим соглашениям следует программист. И эти соглашения могут быть нарушены. Они могут быть нарушены по ошибке, например, из-за простого незнания, или они могут быть нарушены по необходимости. Необходимость может возникнуть в том случае, если нам нужно ручное, низкоуровневое управление динамической памятью. Например, мы пишем свой контейнер. Один из примеров, когда нам нужно ручное управление, мы еще посмотрим в следующих видео. В случае соблюдения этих соглашений, вы можете гарантировать, и это очень важно, и это то, зачем люди придерживаются данных соглашений, вы гарантируете, что в вашей программе не может быть никаких утечек памяти и не может быть двойного удаления объекта. То есть если вы следуете этим соглашениям, у вас такие ситуации просто теоретически невозможны. Нельзя написать такой код. Однако если вы эти соглашения нарушаете, то все может сработать корректно, но при определенных ситуациях вы можете создать программы, в которых будут утечки памяти или двойное удаление. В следующем видео мы как раз посмотрим на такие примеры.