Одна из проблем при написании программ на C ++ заключается в том, что использование динамической памяти может быть проблематичным, поскольку требует ручного управления выделением и освобождением памяти, которую вы хотите использовать. К сожалению, динамическое размещение является важной функцией C ++ и требуется при использовании виртуальных функций для полиморфного поведения. Динамическое выделение также необходимо, если вы намереваетесь хранить данные, размер которых превышает размер стека (более 1 МБ), или данные неизвестного размера во время компиляции, например, данные, полученные в качестве входных данных от пользователя. Есть и другие преимущества, такие как создание динамических структур данных и разрешение объектам существовать за пределами их первоначальной области видимости. Также я упоминал, что вы должны использовать указатели при динамическом распределении памяти.

Проблемы, возникающие при динамическом распределении, связаны с ручным управлением памятью, как упоминалось ранее. Могут возникнуть две важные проблемы: висячие указатели и утечки памяти. Первый относится к экземпляру, в котором указатель пытается получить доступ к памяти, которая была ранее освобождена. Последнее связано с тем, что забыли освободить память, оставив ее недоступной и непригодной для использования во время выполнения программы. Если это делать неоднократно, объем памяти, доступной для других программ, уменьшается, что в конечном итоге приводит к нестабильности и сбоям системы. Таким образом, использование динамической памяти сопряжено со значительным риском, хотя иногда и неизбежным. С выпуском C ++ 11 появились интеллектуальные указатели - функция, которая упрощает работу с динамической памятью.

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

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

Типы

уникальный указатель (unique_ptr) - это интеллектуальный указатель, который имеет семантику исключительного владения. Это означает, что ресурс может принадлежать только уникальному указателю, и когда этот указатель выходит за пределы области видимости, ресурс освобождается. Единственный способ для unique_ptr «поделиться» ресурсом - передать владение этим ресурсом с помощью семантики перемещения.

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

Слабый указатель (weak_ptr) действует как общий указатель в том смысле, что он обеспечивает семантику совместного использования, однако слабый указатель не владеет ресурсом. Это означает, что кто-то другой, разделяемый указатель, уже должен владеть ресурсом, который может совместно использоваться слабым указателем. Кроме того, количество слабых указателей не влияет на счетчик ссылок ресурса, поэтому ресурс будет освобожден, даже если слабые указатели по-прежнему совместно используют этот ресурс. Это означает, что слабый указатель не может получить доступ к общему объекту. Для этого из этого слабого указателя должен быть создан общий указатель. Причина этого заключается в том, что ресурс не может быть уничтожен, пока он используется слабым указателем, потому что будет хотя бы один общий указатель, обращающийся к ресурсу. Это не предотвращает доступ слабого указателя к ресурсу, который больше не существует.

использование

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

Уникальный указатель предназначен исключительно для единоличного владения. Например, если бы у вас был класс движка, который отвечал за создание и инициализацию подсистем движка, таких как средство визуализации или физическая система. Тогда будет разумным предположить, что класс Engine будет единственным владельцем каждой подсистемы и будет хранить каждую подсистему в уникальном указателе. Когда класс Engine уничтожается, что обычно происходит при закрытии использующей его игры, все подсистемы будут отключены, а ресурсы, связанные с этими системами, будут освобождены, когда экземпляр Engine выходит из области видимости. Хотя право собственности остается за движком, к подсистемам по-прежнему могут обращаться другие части игрового движка, обращаясь к указателю, хранящемуся в уникальном указателе, доступ к которому осуществляется через функцию get (). Это позволяет другим объектам получить доступ к ресурсу, желаемой системе.

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

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

В приведенном выше примере ни космический корабль, ни объекты LaserWeapon не будут удалены. Когда создаются оба объекта, для обоих устанавливается счетчик ссылок, равный 1. Когда gameObject содержит ссылку на laserWeapon, счетчик ссылок увеличивается до 2, и то же самое происходит, когда laserWeapon содержит ссылку на spaceShip. Когда программа достигнет конца основного, оба объекта выйдут из области видимости, и, таким образом, счетчик ссылок для обоих уменьшится до 1, потому что мы потеряли одного владельца. Счетчик ссылок laserWeapon не достигнет 0, пока счетчик ссылок spaceShip не достигнет 0, и наоборот, создавая циклическую зависимость, которая приводит к тому, что память обоих никогда не удаляется. Ни один из счетчиков ссылок не достигает 0, поэтому C ++ не считает, что память нужно освобождать.

использованная литература