C++ 11
Язык C++ сильно изменился с выходом нового стандарта, C++ 2011. Сменилась генеральная линия партии: теперь разработчикам не рекомендуется вообще использовать new и delete, потому что всё можно сделать проще без них. Вот сразу пример:
Кратко о выделении памяти
В C++ есть два места, где можно размещать переменные — это стек (stack) и динамическая куча (heap).
- Стек выделяется для каждого потока каждой программы операционной системой. При вызове функции стек растёт, потому что через вершину стека передаются параметры функции и адрес возврата. В начале работы функции стек растёт ещё раз, потому что в нём располагаются локальные переменные. При выходе из функции размер стека уменьшается.
- Куча не размечена, операционная система даёт программисту возможность выделять или освобождать отдельные куски этой кучи. При этом куча подвержена фрагментации, и нет гарантий, что при общей нехватке памяти в системе запрошенный программой кусок памяти удастся выделить.
Кратко о семантике владения
Пожалуй, самая популярная в наше время парадигма программирования - объектная. В рамках парадигмы любая программа представляется как набор объектов, например:
- Объект Application, который хранит основной цикл интерактивного приложения
- Абстрактный объект Shape (т.е. фигура без каких-либо уточнений) и конкретные классы RectangleShape, CircleShape и так далее.
- Объекты Enemy и EnemyBehavior, хранящие соответственно визуальное представление и поведение врага в игре
Обычно любым объектом (кроме самого главного) кто-то владеет. Иначе говоря, кто-то
- создаёт объект сразу или по мере необходимости
- управляет поведением объекта или даёт управлять им кому-то ещё
- удаляет объект
Отношение владения формирует иерархию объектов. Иерархия хорошо представляется в виде дерева — в дереве легко понять, кто кем владеет:
- корень дерева прямо или косвенно владеет всем, корнем не владеет никто
- листья дерева никем не владеют
Кстати говоря, дерево является графом без циклов.
Сильные указатели
Типы std::unique_ptr<T> и std::shared_ptr<T> являются умными указателем, подходящим для древовидной иерархии владения. Советы:
- Используйте shared_ptr, если объектом владеют несколько родителей. Например, если одна и та же картинка используется в качестве фона нескольких кнопок.
- Используйте функцию std::make_shared для создания shared_ptr, это лучше в плане читаемости и скорости выполнения кода.
- Используйте функцию std::make_unique для создания unique_ptr, это лучше в плане читаемости кода.
Слабые указатели
Допустим, что в нашем дереве мы захотим иметь ссылку из дочернего узла на родительский, или даже на соседнюю ветку. Например, хотим в объекте-враге хранить указатель на цель, котоую он сейчас атакует.
Если мы создадим shared_ptr, в графе владения возникнет цикл.
Циклы удаляются только в средах выполнения со сборкой мусора (именно в этом причина снижения производительности из-за сборки мусора в «современных» языках, таких как Java и C#).
Во избежание создания циклов используйте weak_ptr для ссылания на родителя или другую ветвь. Суть weak_ptr очень проста:
- он никак не владеет тем, на что указывает, и не меняет время жизни
- значение указателя внутри weak_ptr обратится в ноль в тот момент, когда объект, на который указывает weak_ptr, уничтожится.
- weak_ptr тесно связан с shared_ptr, и они взаимно приводятся друг в друга
Применения семантики владения
Семантика владения, основанная на unique_ptr, shared_ptr, weak_ptr и полном отсутствии new/delete, позволяет проще и надёжнее писать различные программы:
- Игры, применяющие паттерн SceneGraph и обрабатывающие физические коллизии между объектами
- Компиляторы, использующие Abstract Syntax Tree для промежуточного хранения модели кода
- WYSIWYG-редакторы, загружающие модель документа в память программы и отображающие документ на экране