Динамическое управление памятью
В 11 стандарте С++ появились полезные умные указатели управления динамической памятью std::unique_ptr, std::shared_ptr и std::weak_ptr.
std::unique_ptr – умный указатель, который: получает единоличное владение объектом через его указатель, и разрушает объект через его указатель, когда unique_ptr выходит из области видимости.
unique_ptr не может быть скопирован или задан через операцию присвоения. Неконстантный unique_ptr может передать владение управляемым объектом другому указателю unique_ptr. Сonst std::unique_ptr не может быть передан, ограничивая время жизни управляемого объекта областью, в которой указатель был создан. Когда unique_ptr уничтожается, он удаляет объект с помощью deleter.
Существует две версии std::unique_ptr:
1) управляет временем жизни одного объекта, например, созданного с помощью оператора new;
2) управляет временем жизни массива, с длиной, определенной во время выполнения, созданного с помощью new[].
Типичные случаи применения std::unique_ptr включают:
· обеспечение безопасности исключений для классов и функций, которые управляют объектами с динамическим временем жизни, гарантируя удаление в случае нормального завершения и завершения по исключению;
· передача владения динамически созданным объектом в функции;
· получение владения динамически созданным объектом из функций;
· в качестве типа элемента в контейнерах, поддерживающих семантику перемещения, таких как std::vector, которые хранят указатели на динамически выделенные объекты (например, если желательно полиморфное поведение).
#include "stdafx.h"
#include <iostream>
#include <memory>
struct Foo {
Foo() { std::cout << "Foo::Foo\n"; }
~Foo() { std::cout << "Foo::~Foo\n"; }
void bar() { std::cout << "Foo::bar\n"; }
};
void f(const Foo &foo)
{
std::cout << "f(const Foo&)\n";
}
int main()
{
std::unique_ptr<Foo> p1(new Foo); // p1 владеет Foo
if (p1) p1->bar();
{
std::unique_ptr<Foo> p2(std::move(p1)); // теперь p2 владеет Foo
f(*p2);
p1 = std::move(p2); // владение возвращено p1
std::cout << "destroying p2...\n";
}
if (p1) p1->bar();
}
std::shared_ptr – умный указатель, с разделяемым владением объектом через его указатель. Несколько указателей shared_ptr могут владеть одним и тем же объектом; объект будет уничтожен, когда последний shared_ptr, указывающий на него, будет уничтожен или сброшен. Объект уничтожается с использованием delete-expression или с использованием пользовательской функции удаления объекта, переданной в конструктор shared_ptr.
shared_ptr может не владеть ни одним объектом, в этом случае он называется пустым.
В типичной реализации, std::shared_ptr содержит только два указателя:
· указатель на объект владения;
· указатель на блок управления;
где блок управления является динамически-созданным объект, который содержит:
· указатель на управляемый объект или сам управляемый объект;
· функцию удаления объекта;
· аллокатор;
· счетчик указателей shared_ptr, владеющих управляемым объектом;
· счетчик указателей weak_ptr, которые ссылаются на управляемый объект.
Деструктор shared_ptr уменьшает счетчик общих владельцев в контрольном блоке, и если этот счетчик достиг нуля, блок управления вызывает деструктор управляемого объекта, однако блок управления не освобождает себя до тех пор, пока счетчик std::weak_ptr также не достигнет нуля.
std::weak_ptr – умный указатель, который содержит "слабую" ссылку на объект, управляемый указателем std::shared_ptr. Чтобы получить доступ к управляемому объекту, указатель необходимо привести к типу std::shared_ptr, .
std::weak_ptr моделирует временное владение: когда объект должен быть доступен только если он существует и может быть удален в любой момент кем-то другим, std::weak_ptr используется для отслеживания объекта, и преобразуется в std::shared_ptr для принятия временного владения. Если исходный std::shared_ptr будет уничтожен в процессе работы, время жизни объекта продлевается до того момента, пока не будет разрушен временный std::shared_ptr.
Помимо этого, std::weak_ptr используется для устранения циклических ссылок std::shared_ptr. Например, в таблице ниже приведён пример возникновения циклической сылки при использовании shared_ptr, а также решение этой проблемы с помощью weak_ptr.
Циклическая ссылка, которя приводит к утечке памяти. | Устранение проблемы циклической ссылки. |
#include “stdafx.h” #include <iostream> #include <memory> using namespace std; struct Child; struct Parent { shared_ptr<Child> child; ~Parent() { cout << “Bye Parent” << endl; } void hi() const { cout << “Hello” << endl; } }; struct Child { shared_ptr<Parent> parent; ~Child() { cout << “Bye Child” << endl; } }; int main() { auto parent = make_shared<Parent>(); //3 auto child = make_shared<Child>(); //4 parent->child = child; //5 child->parent = parent; //6 child->parent->hi(); } //7 | #include “stdafx.h” #include <iostream> #include <memory> using namespace std; struct Child; struct Parent { shared_ptr<Child> child; //1 ~Parent() { cout << “Bye Parent” << endl; } void hi() const { cout << “Hello” << endl; } }; struct Child { weak_ptr<Parent> parent; //2 ~Child() { cout << “Bye Child” << endl; } }; int main() { auto parent = make_shared<Parent>(); //3 auto child = make_shared<Child>(); //4 parent->child = child; //5 child->parent = parent; //6 child->parent.lock()->hi(); } //7 |
Hello | Hello Bye Parent Bye Child |
В функции main() в строке //3 с помощью функции make_shared создаются в динамической памяти объекты: Parent и блок управлния этим объектом. Кроме того, функция make_shared возвращает объект типа shared_ptr, который содержат два указателя (на объект Parent и блок управлния этим объектом). В блоке управления есть два счётчика (sp и wp), в которых хранится количество существующих в программе указателей типа shared_ptr и в weak_ptr на объект Parent соответственно. После выполнения этой строки sp = 1, а wp = 0. | |
В функции main() в строке //4 с помощью функции make_shared создаются в динамической памяти объекты: Child и блок управлния этим объектом. Кроме того, функция make_shared возвращает объект типа shared_ptr, который содержат два указателя (на объект Child и блок управлния этим объектом). В блоке управления есть два счётчика (sc и wc), в которых хранится количество существующих в программе указателей типа shared_ptr и в weak_ptr на объект Child соответсвенно. После выполнения этой строки sc = 1, а wc = 0. | |
После выполнения строки //5 в программе теперь два указателя типа shared_ptr на объект Child, следовательно, в блоке управления этим объектом счётчики станут равными sс = 2, а wс = 0 | |
После выполнения строки //6 в программе теперь два указателя типа shared_ptr на объект Parent, следовательно, в блоке управления этим объектом счётчики станут равными sp = 2, а wp= 0 | После выполнения строки //6 в программе теперь коме shared_ptr есть указателя типа weak__ptr на объект Parent, следовательно, в блоке управления этим объектом счётчики станут равными sp = 1, а wp = 1 |
В строке //7 начинают уничтожаются локальные объекты функции main, т.е. указатели auto parent и auto child. В результате в блоках управления этих указателей счётчики становятся равными sp = 1, wp = 0 и sс = 1, а wс = 0, соотетственно. Поскольку счётчики sp и sc не равны нулю, то деструкторы для объектов Parent и Child не вызываются. Таким образом, они остаются в динамической памяти, что приводит к её утечке. | В строке //7 начинают уничтожаются локальные объекты функции main, т.е. указатели auto parent и auto child. В результате в блоках управления этих указателей счётчики становятся равными sp = 0, wp = 1 и sс = 1, wс = 0, соотетственно. Поскольку счётчики sp = 0, то вызывается деструктор для объекта Parent и далее деструкторы для компонентных данных объекта Parent, т.е. деструктор для указателя shared_ptr<Child> child из строки //1, в результате sс = 0, а wс = 0. Как только sc = 0, сразу вызывается деструктор для объекта Child, который в свою очередь вызывате деструктор для weak_ptr<Parent> parent из строки //2 (хотя объект Parent уже разрушен, но блок управления им ещё нет, так как wp = 1), который обнуляет wp. После чего блок управления объектом Parent тоже разрушается. Таким образом, все динамические объекты разрушаются, и динамическая память полностью освобождается. |
Модуль 6
Дата добавления: 2020-12-11; просмотров: 396;