M.x – Резюме к главе M и небольшой тест
Класс умного указателя – это класс композиции, который предназначен для управления динамически выделяемой памятью и обеспечения освобождения этой памяти, когда объект умного указателя выходит за пределы области видимости.
Семантика копирования позволяет копировать объекты наших классов. В основном это делается с помощью конструктора копирования и оператора присваивания копированием.
Семантика перемещения означает, что объект класса передает владение ресурсом, а не выполняет его копирование. В основном это делается с помощью конструктора перемещения и оператора присваивания перемещением.
std::auto_ptr
устарел, и его следует избегать.
rvalue-ссылка (ссылка на r-значение) – это ссылка, которая предназначена для инициализации с помощью r-значения. rvalue-ссылка создается с использованием двойного амперсанда. Можно писать функции, которые принимают в качестве параметров rvalue-ссылки, но вы почти никогда не должны возвращать rvalue-ссылку.
Если мы конструируем объект или выполняем присваивание, в котором аргументом является l-значение, единственное приемлемое действие – это выполнить копирование этого l-значения. Мы не можем предположить, что изменение l-значения безопасно, потому что позже в программе оно может быть использовано снова. Если у нас есть выражение a = b
, мы не можем ожидать каких-либо изменений b
.
Однако если мы конструируем объект или выполняем присваивание, в котором аргументом является r-значение, то мы знаем, что это r-значение – это всего лишь временный объект какого-то типа. Вместо того, чтобы копировать его (что может быть дорогостояще), мы можем просто передать его ресурсы (что дешево) объекту, который мы создаем или которому выполняем присваивание. Это безопасно, потому что временный объект в любом случае будет уничтожен в конце выражения, поэтому мы знаем, что он больше никогда не будет использоваться!
Для создаваемых вами классов вы можете отключить семантику копирования, используя ключевое слово delete
и удалив конструктор копирования и оператор присваивания копированием.
std::move
позволяет рассматривать l-значение как r-значение. Это полезно, когда мы хотим использовать для l-значения семантику перемещения вместо семантики копирования.
std::move_if_noexcept
вернет перемещаемое r-значение, если у объекта есть конструктор перемещения, отмеченный как noexcept
; в противном случае он вернет копируемое l-значение. Мы можем использовать спецификатор noexcept
в сочетании с std::move_if_noexcept
, чтобы использовать семантику перемещения только при наличии строгой гарантии безопасности исключений (и использовать семантику копирования в противном случае).
std::unique_ptr
– это класс умных указателей, который вам, вероятно, следует использовать, если необходимо управлять одним ресурсом, не предназначенным для совместного использования. Для создания нового std::unique_ptr
следует предпочитать использовать std::make_unique()
(в C++14). std::unique_ptr
отключает семантику копирования.
std::shared_ptr
– это класс умных указателей, используемый, когда вам нужно, чтобы несколько объектов обращались к одному и тому же ресурсу. Ресурс не будет уничтожен до тех пор, пока не будет уничтожен последний управляющий им std::shared_ptr
. Для создания нового std::shared_ptr
следует предпочитать использовать std::make_shared()
. Для создания дополнительных std::shared_ptr
, указывающих на тот же объект, следует использовать семантику копирования.
std::weak_ptr
– это класс умных указателей, используемый, когда вам нужен один или несколько объектов с возможностью просмотра и доступа к ресурсу, управляемому std::shared_ptr
, но в отличие от std::shared_ptr
, std::weak_ptr
не учитывается при определении того, должен ли ресурс быть уничтожен.
Небольшой тест
Вопрос 1
Объясните, когда следует использовать указатели следующих типов.
1a) std::unique_ptr
Ответ
std::unique_ptr
следует использовать, когда вы хотите, чтобы умный указатель управлял динамическим объектом, который не будет использоваться совместно с кем-либо еще.
1b) std::shared_ptr
Ответ
std::shared_ptr
следует использовать, когда вы хотите, чтобы умный указатель управлял динамическим объектом, который может быть общим и использоваться совместно с другимиstd::shared_ptr
. Объект не будет освобожден, пока всеstd::shared_ptr
, содержащие объект, не будут уничтожены.
1c) std::weak_ptr
Ответ
std::weak_ptr
следует использовать, когда вы хотите получить доступ к объекту, которым управляетstd::shared_ptr
, но не хотите, чтобы время жизниstd::shared_ptr
было привязано к времени жизниstd::weak_ptr
.
1d) std::auto_ptr
Ответ
std::auto_ptr
устарел и удален в C++17. Его нельзя использовать.
Вопрос 2
Объясните, почему семантика перемещения сосредоточена на r-значениях.
Ответ
Поскольку r-значения являются временными, мы знаем, что они будут уничтожены после использования. При передаче или возврате r-значения по значению расточительно делать копию, а затем уничтожать оригинал. Вместо этого мы можем просто переместить (забрать) ресурсы r-значения, что обычно более эффективно.
Вопрос 3
Что не так со следующим кодом? Обновите программы, чтобы они соответствовали передовой практике.
3а)
#include <iostream>
#include <memory> // для std::shared_ptr
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
};
int main()
{
auto* res{ new Resource{} };
std::shared_ptr<Resource> ptr1{ res };
std::shared_ptr<Resource> ptr2{ res };
return 0;
}
Ответ
ptr2
был создан изres
, а не изptr1
. Это означает, что теперь у вас есть дваstd::shared_ptr
, каждый из которых пытается управлять ресурсом независимо (они не знают друг о друге). Когда один выходит за пределы области видимости, у другого останется висячий указатель.
ptr2
следует скопировать изptr1
, а не изres
, и следует использоватьstd::make_shared()
.#include <iostream> #include <memory> // для std::shared_ptr class Resource { public: Resource() { std::cout << "Resource acquired\n"; } ~Resource() { std::cout << "Resource destroyed\n"; } }; int main() { auto ptr1{ std::make_shared<Resource>() }; auto ptr2{ ptr1 }; return 0; }
3b)
#include <iostream>
#include <memory> // для std::shared_ptr
class Something; // предполагаем, что Something - класс, который может вызвать исключение
int main()
{
doSomething(std::shared_ptr{ new Something{} }, std::shared_ptr{ new Something{} });
return 0;
}
Ответ
Если конструктор
Something
выдает исключение, один из объектовSomething
может быть не удален правильно.Решение – использовать
make_shared
:#include <iostream> #include <memory> // для std::shared_ptr class Something; // предполагаем, что Something - класс, который может вызвать исключение int main() { doSomething(std::make_shared<Something>(), std::make_shared<Something>()); return 0; }