M.x – Резюме к главе M и небольшой тест

Добавлено 23 сентября 2021 в 13:18

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

Семантика копирования позволяет копировать объекты наших классов. В основном это делается с помощью конструктора копирования и оператора присваивания копированием.

Семантика перемещения означает, что объект класса передает владение ресурсом, а не выполняет его копирование. В основном это делается с помощью конструктора перемещения и оператора присваивания перемещением.

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;
}

Теги

C++ / CppLearnCppr-value / r-значениеstd::auto_ptrstd::make_sharedstd::make_uniquestd::shared_ptrstd::unique_ptrstd::weak_ptrДля начинающихОбучениеПрограммированиеСемантика перемещенияУмные указатели

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.