Что такое умный указатель? Зачем использовать умные указатели?

C++

Что такое умный указатель? Зачем использовать умные указатели? Как решить проблему циклической ссылки? Какова роль управления ресурсами?

Когда я увидел эти вопросы, у меня сжалось сердце. Что такое умный указатель? Зачем использовать умные указатели? Что, черт возьми, за круговая ссылка? выполнить? я ❌...

Сначала давайте посмотрим на первый вопрос, что такое умный указатель?

Существует несколько общих умных указателей, один — общий указатель shared_ptr, другой — эксклюзивный указатель unique_ptr, третий — слабый указатель weak_ptr, а третий — auto_ptr, который давно не использовался (заменен на unique_ptr) .
Умные указатели тоже являются указателями, и они тоже относятся к категории указателей, но они умнее обычных указателей, указывая на то, что они умные указатели, поэтому они должны быть умнее. Так же, как разница между умным домом и обычным домом. Так насколько это умно? Мы знаем, что динамическая память, то есть когда мы обычно обращаемся за памятью в куче, если мы используем указатели, следующим образом

T *p = new T();
delete p;

Им нужно управлять, например, удалять,delete和newэто пара операций,delete[]和new[]Оба они появились как пара CP, чтобы оскорбить единую группу программистов 🐶. Такая операция, как new, выделяет место для объекта и возвращает указатель на этот объект. Итак, возвращается указатель, далее нам нужно управлять этим указателем, удалить — принять указатель на динамический объект, уничтожить объект (вызов деструктора, кроме встроенных типов, с ним не заниматься), и освободить память, связанная с ним.
Вы позволили одному 🐶 справиться? Разве это не шутка? такстандартная библиотека(помните, что он предоставляется стандартной библиотекой), определенный в заголовочном файле<memory>, представляет собой умную указку, которая во многом способствовала построению гармоничного и красивого общества.
Интеллектуальные указатели ведут себя как обычные указатели, только здесь они похожи, потому что интеллектуальные указатели отвечают за автоматическое освобождение объектов, на которые они указывают. (Вообще пусть цп откатывается 🐶🐶🐶)
И указатель не говорит, кому принадлежит объект, он не знает, кому он принадлежит, но умный указатель знает.

Эксклюзив одного человека, ощущение, что он владеет всем миром (эксклюзивное владение unique_ptr)

    unique_ptr<string> p1(new string("hi,world")); // 必须采用直接初始化的形式初始化
    unique_ptr<string> p2(p1); // ❌ 不支持拷贝
    unique_ptr<string> p3;
    p3 = p2; // ❌ 不支持赋值

Делает ли приведенный выше код, чтобы unique_ptr чувствовал себя очень комфортно, мои вещи мои, вы не можете сохранить копию, это единственная. Он имеет в виду взаимоисключающую собственность. Общие указатели поддерживают операции присваивания копирования, но здесь он использует как конструктор копирования, так и оператор присваивания копирования.deleteТеперь вам вообще не разрешат копировать.

  • A unique_ptr "Иметь" объект (точка указывает), в каждый момент времени только одна уникальная_ptr указывает на данный объект. Когда UNIQUE_PTR уничтожается, указатель объекта также уничтожается.
  • unique_ptr не может быть скопирован, не может быть назначен и может быть перемещен (p.release())
unique_ptr<string> p1(new string("hi"));
unique_ptr<string> p2(p1.release()); // 将p1置为空,返回指针
cout << *p2 << endl;
unique_ptr<string> p3(new string("hello,world"));
p2.reset(p3.release()); // reset释放了p2原来指向的内存 然后令p2指向p3所指向的对象,然后release()将p3置为空
cout << *p3 << endl; // 输出的都是hi
cout << *p1 << endl; // p1已经被释放了,没有了

Так у него столько ограничений, какая реализация в стандартной библиотеке? Взгляните на следующий абзац

template<typename T,typename D = default_delete<T> > // default_delete是一个无状态类
class unique_ptr{
    public:
        using pointer = ptr;
        using element_type = T;
        using deleter_type = D;
        constexpr unique_ptr() noexcept;
        constexpr unique_ptr(nullptr_t) noexcept:unique_ptr(){} // 空指针类型
        explicit unique_ptr(pointer p) noexcept; // from pointer
        unique_ptr(pointer p,typename conditional<is_reference<D>::value,D,const D&> del) noexcept; // lvalue
        unique_ptr(pointer p,typename remove_reference<D>::type&& del) noexcept; // rvalue
        unique_ptr(unique_ptr&& x) noexcept;// 右值 移动构造函数
        template<class U,class E>
            unique_ptr(unique_ptr<U,E>&& x)noexcept; // 特例化
        template<class U>
            unique_ptr(auto_ptr<U>&& x)noexcept; // 不抛出异常
        Unique_ptr(const unique_ptr&) = delete; // 不允许拷贝

        unique_ptr& operator=(unique_ptr&& x) noexcept; // 移动赋值运算
        unique_ptr& operator=(nullptr_t) noexcept; // 空指针类型
        template<class U,class E>
            unique_ptr& operator=(unique_ptr<U,E>&& x)noexcept; // 强制类型转换
        unique_ptr& operator=(const unique_ptr&) = delete; // 不允许赋值
};

Чтобы не выбрасывать исключение, устанавливается noexpect, который включает в себя операции со ссылками на rvalue, операции со ссылками на lvalue и операцию специализации конструктора. Он в основном охватывает и объясняет, почему он не копируется и не назначается.
выпуск() и сброс()Обе функции передают владение указателем от одного (неконстантного) unique_ptr другому unique_ptr.
reset() может быть лучше и может освобождать память, но функция release() не будет работать, функция release() должна иметьПолучатель, то он может либо автоматически отвечать за выпуск, либо отвечать за выпуск вручную.
Далее, давайте посмотрим на реализацию этих двух

void reset(pointer p = pointer()) noexcept;
// 这里有一个默认值
pointer release() noexcept;
// 这里返回一个值

release() возвращает указатель, поэтому ему нужен ловец.

  • unique_ptr сохраняет указатель, и когда он уничтожается (например, когда поток управления потоком выходит из области действия unique_ptr), используйте связанный релизер (deleter), чтобы освободить указанный объект
    Что такое релизер? Когда unique_ptr уничтожается, вызывается его собственный релизер для уничтожения принадлежащего ему объекта.
deleter_type& get_deleter() noexcept;
const deleter_type& get_deleter() const noexcept;
  1. Релизер локальной переменной ничего не должен делать
  2. Пул памяти должен возвращать объект в пул памяти, следует ли уничтожить его, зависит от того, как определен пул памяти.
  3. По умолчанию удаление вызывается для освобождения объекта, на который оно указывает.
    Релиз управления делится на привязку во время выполнения и привязку во время компиляции.Эти два различия относятся к разнице между shared_ptr и unique_ptr.После того, как shared_ptr будет объяснен ниже, просто помните, что unique_ptr управляет релизером при компиляции.
    Итак, как пройти релизер? Давайте посмотрим один 🌰
#include <memory>
#include <iostream>
#include <string>
using namespace std;

class Role{
    public:
        Role(const string &crole):role(crole){
            cout << role << endl;
        }
        ~Role(){
            cout << "delete" << endl;
        }
        void delRole(){
            cout << "delete Role outside" << endl;
        }
    private:
        string role;
};

void outdelRole(Role *r){
    r->delRole();
}

int main(){
    unique_ptr<Role,decltype(outdelRole)*> p1(new Role("trans"),outdelRole);
    return 0;
}

выход транс удалить роль снаружи
Это 🌰 полностью показывает, что мы можем перегрузить релизер, если это релизер функции, то его тип параметра должен быть указателем типа objT, чтобы его было осмысленно удалять. decltype обычно используется для указания типа

unique_ptr<objT,delT>p(new objT,fcn); // fcn是delT类型对象

Таким образом, вам решать, как вы хотите удалить и что удалить.
также могу сделать

#include <iostream>
#include <memory>
using namespace std;
class state_deleter {  // a deleter class with state
  int count_;
public:
  state_deleter() : count_(0) {}
  template <class T>
  void operator()(T* p) {
    cout << "[deleted #" << ++count_ << "]\n";
    delete p;
  }
};

state_deleter del;
unique_ptr<int,state_deleter> alpha (new int);
unique_ptr<int,state_deleter> beta (new int,alpha.get_deleter());

// gamma and delta share the deleter "del" (deleter type is a reference!):
unique_ptr<int,state_deleter&> gamma (new int,del);
unique_ptr<int,state_deleter&> delta (new int,gamma.get_deleter());

Давайте посмотрим на фрагмент кода, который сравнивает ловушки

unique_ptr<string> p1;
cout << *p1 << endl;

Этот код представляет, что p1 является нулевым указателем, тогда этот нулевой указатель не указывает на объект, как насчет следующего абзаца?

unique_ptr<string> p1();
cout << *p1 << endl;

Вывод 1, почему? потому чтоunique_ptr<string> p1()Объявить функцию без параметров p1, тип возвращаемого значения - указатель типа unique_ptr, поэтому если *p1, то может быть только 1, он является телом функции

использовать

  • Обеспечивает безопасность исключений для динамически выделяемой памяти.
    unique_ptr можно понимать как простой указатель (указывающий на объект) или пару указателей (включая случай удаления релизера)
  • Передать право собственности на динамически выделенную память функции
  • Возврат динамически выделенной памяти из функции
  • сохранить указатель из контейнера
    ⚠️Вот использование get()
pointer get() const noexcept;

get() — это указатель на управляемый объект или нулевой указатель.

unique_ptr<string> p1(new string("hello world"));
string *pstr = p1.get();
cout << *pstr << endl;

Он отличается от release(), он только управляемый, get не указывает pstr на объект, на который указывает p1, но и не освобождает память p1, pstr не получает в собственность умный указатель, а просто получает его объект. p1 по-прежнему необходимо удалить управляемые данные pstr в какой-то момент.
Давайте снова посмотрим на оператор разыменования

typename add_lvalue_reference<element_type>::type operator*() const;

Функция поддерживает работу с указателем

unique_ptr<string> p1(new string("hello world"));
cout << *p1 << endl;

Посмотрите еще раз на оператор ->

pointer operator->()const noexcept;

Операции, поддерживающие поведение указателя

  unique_ptr<C> foo (new C);
  unique_ptr<C> bar;

  foo->a = 10;
  foo->b = 20;

  bar = std::move(foo); // 支持右值移动操作 foo就释放了

Что ж, мы знаем, что весь unique_ptr будет поддерживать поведение указателей, так что давайте посмотрим на его специализированную версию. Что такое специализация? Это особое лечение для особых 🌰. разные версии

template<class T,class D> class unique_ptr<T[],D>;
// 用于内置函数
unique_ptr<int[]> make_sequence(int n){
    unique_ptr<int[]> p{new int[n]};
    for(int i = 0;i<n;++i)
        p[i] = i;
    return p; // 返回局部对象
}

Разумеется, здесь добавлена ​​функция уникального оператора [], то есть перегрузка оператора [].

element_type& operator[](size_t i)const;

Вместо того, чтобы беспокоиться о сопоставлении, мы предоставляем специальную версию, чтобы помочь компилятору выполнить работу по сопоставлению.
Как насчет замены указателей? Обмен также является мобильной операцией!

template <class T,class D>
void(unique_ptr<T,D>& x,unique_ptr<T,D>& y)noexpect;

Обменяйтесь собственностью двух сторон, вы хотите мою, я хочу вашу. Конечно, это функция, не являющаяся членом, а также существуют функции-члены.

void swap(unique_ptr& x) noexcept;

то естьa.swap(b)Соус фиолетовый.

Общие объекты 😁, ваши объекты, которыми я делюсь ✨o✨ (shared_ptr совместное владение)

Теперь, когда мы закончили говорить об unique_ptr, давайте поговорим об этом shared_ptr, общем указателе, который делает общество лучше.
Давайте посмотрим, как использовать

shared_ptr<string> p1; 
shared_ptr<list<int> > p2;

При инициализации по умолчанию и p1, и p2 являются нулевыми указателями. Конечно, ни одна из этих двух операций не выделяет и не использует динамическую память. как нам это сделать? Мы пробуем это.

shared_ptr<string> p1(new string("hehehe"));
cout << *p1 << endl;

Вы также можете попробовать это

shared_ptr<int> clone(int p){
    return shared_ptr<int>(new int(p));
}

Также может управлять встроенным указателем inum

int *inum = new int(42);
shared_ptr<int> p2(inum);

Стоп🤚стоп🤚стоп🛑, позвольте мне сначала объяснить, что такое общий указатель shared_ptr?
Shared_ptr указывает на совместное владение.В отличие от указателей unique_ptr, shared_ptr может совместно использовать объект. Shared_ptr можно использовать, когда двум частям кода необходимо получить доступ к одним и тем же данным, но ни одна из них не имеет монопольного владения (отвечает за уничтожение объекта). shared_ptr — это указатель счетчика, когда счетчик (use_count) становится равным 0, объект, на который указывает указатель, освобождается.
Его можно понимать как структуру, содержащую два указателя, один указатель указывает на объект, а другой указатель указывает на счетчик (use_count).
Но только потому, что объект, на который указывает указатель, уничтожается, когда счетчик становится равным 0, его релизер (deleter) — это не то же самое, что unique_ptr, это функция, не являющаяся членом. Но это вызываемый объект, о вызываемом объекте я расскажу позже, а здесь надо понимать, что shared_ptr — это релизер.привязка во время выполнения, вместопривязывать во время компиляциииз. И unique_ptr — это релизер, связанный во время компиляции. Релиз по умолчаниюdelete, это не изменилось. (вызывает деструктор объекта и освобождает память)
Его внимание сосредоточено на подсчете использования, так как же определяется этот подсчет? Посмотрите на кусок кода.

shared_ptr<int> p3 = make_shared<int>(42);
cout << p3.use_count() << endl;

Смотрите, use_count() здесь используется для подсчета, а теперь это 1, то есть на этот объект ссылаются один раз.

    shared_ptr<int> p3 = make_shared<int>(42);
    auto r = p3;
    cout << p3.use_count() << endl;

Здесь 2, что здесь произойдет, увеличить счетчик ссылок p3, а как насчет r? Каково число r? r равно 2, что означает, что этот r также указывает на объект p3, тогда счетчик должен быть таким же. Что если у r изначально был объект, на который можно было бы указать? Счетчик объекта, на который указывает исходный r, также уменьшается, а другие указатели не затрагиваются.
Так что на самом деле разница в том, что ни один из этих указателей общего владения не имеет права убивать объект, и он отдает на аутсорсинг уничтожение объекта. (Я не могу этого вынести! 😖).
Так-так, раз есть счетчик, можно сказать, что shared_ptr автоматически уничтожает управляемый объект. Также можно сказать, что shared_ptr автоматически освобождает связанную память.
Вы можете взглянуть на этот код, чтобы увидеть использование динамической памяти

#include <iostream>
#include <memory>
#include <string>
#include <initializer_list>
#include <vector>

using namespace std;

class StrBlob{
    public:
        typedef vector<string>::size_type size_type;
        StrBlob():data(make_shared<vector<string> >()){}
        StrBlob(initializer_list<string> il):data(make_shared<vector<string> >(il)){} // 使用参数列表初始化vector
        size_type size() const { return data->size();}
        bool empty() const { return data->empty();}
        void push_back(const string &t){return data->push_back(t);}
        void pop_back();
        string &front();
        string &back();
    private:
        shared_ptr<vector<string> > data; // 共享同一个数据?
        void check(size_type i,const string &msg) const;
};

Когда мы копируем, назначаем или уничтожаем объект StrBlob, элементы данных этого shared_ptr будут скопированы, назначены и уничтожены. Затем каждый раз выполняется безопасная операция, автоматически отпускаемая. Сейф из-за счетчика.
Так что это не сложно, просто надеюсь, что мы сможем использовать shared_ptr для управления ресурсами динамической памяти. Здесь я остановлюсь на этом позже (RAII)
хорошо, после прочтения управления ресурсами в динамической памяти, что такое динамическая память, с которой мы знакомы? Это пара cps, то есть new и delete. На самом деле, shared_ptr и new также можно использовать вместе.

shared_ptr<double> p1; // shared_ptr 可以指向一个double
shared_ptr<int> p2(new int(42)); // p2指向一个值42的int 直接初始化形式

Смотрим на конструктор

    template<typename U>
    class shared_ptr{
        public:
            using element_type = U;
            constexpr shared_ptr() noexcept;
            constexpr shared_ptr(nullptr_t):shared_ptr(){} // 空对象
            template <class U> explicit shared_ptr(U* p); // 显式构造 不存在隐式转换
            template <class U,class D> shared_ptr(U* p,D del); // 添加释放器
            template <class D> shared_ptr(nullptr_t p,D del); // 空指针的释放器
            template <class U,class D, class Alloc> shared_ptr(U* p,D del,Alloc alloc); // 分配?
            template <class D,class Alloc> shared_ptr(nullptr_t p,D del,Alloc alloc);
            shared_ptr(const shared_ptr& x) noexcept;
            template<class U> shared_ptr(const shared_ptr<U>& x)noexcept;
            template<class U> explicit shared_ptr(const weak_ptr<U>& x);
            shared_ptr(shared_ptr&& x)(shared_ptr<U>&& x)noexcept; // 右值移动
            template <class U> shared_ptr(auto_ptr<U>&& x);
            template <class U,class D> shared_ptr(unique_ptr<U,D>&& x);// 获得独享指针的所有权
            template <class U> shared_ptr(const shared_ptr<U>& x,element_type* p)noexcept;
    };

В конструкторе конструктор интеллектуального указателя, который принимает параметр указателя,explicit, что является явной конструкцией, а не неявным преобразованием.

shared_ptr<int> clone(int p){
    return shared_ptr<int>(new int(p));
}

В грунтовке рекомендуетсяНе смешивайте обычные указатели и умные указатели, как это считается смешанным? Давайте посмотрим, что это дает 🌰.

void process(shared_ptr<int> ptr){
    // 使用ptr
}// ptr离开作用域,被销毁

В этом 🌰 ptr — это передача значения. Все мы знаем, что передача значения увеличивает стоимость копирования, создания и т. д., поэтому значение счетчика ptr равно как минимум 2, что справедливо. Когда процесс завершается, значение счетчика не станет 0. Таким образом, локальная переменная ptr уничтожается, и память, на которую указывает ptr, не освобождается. (Поэтому использование ссылок будет уменьшать и увеличивать количество ссылок)

void process(shared_ptr<int>& ptr){
    cout << ptr.use_count() << endl;
    cout << *ptr << endl;
}

Когда мы используем передачу по значению, счетчик ссылок равен как минимум 2, но при передаче по ссылке счетчик ссылок не увеличивается.

    shared_ptr<int> p3 = make_shared<int>(42);
    cout << p3.use_count() << endl;
    // auto r = p3;
    // cout << r.use_count() << endl;
    process(p3);
    cout << p3.use_count() << endl;

При подсчете ссылок вывод согласован.
Кажется, что этот 🌰 можно использовать только для передачи ссылок и значений, что, похоже, не имеет ничего общего со смешиванием обычных указателей и умных указателей!

    int *x(new int(9));
    process(shared_ptr<int>(x));
    int j = *x;
    cout << j << endl;

Выше 🌰 мы используем передачу по значению. В порядке. Что это 🌰 означает? может не понятьshared_ptr<int>(x)Эта своего рода трусливая операция, посмотрим, заставит ли это вас немного понять

shared_ptr<int> ptr = shared_ptr<int>(new int(10));

понятно.

shared_ptr<T> p(q);

q — это встроенный указатель, а p управляет объектом, на который указывает этот встроенный указатель. q должен указывать на память, выделенную оператором new, и быть конвертируемым в тип T*.
Таким образом, приведенный выше пример показывает, что когда эти два элемента используются вместе, временный shared_ptr будет уничтожен, а память, на которую он указывает, также будет освобождена. Таким образом, предполагается, что x по-прежнему указывает на эту память, но x неосознанно стал оборванным указателем.
На самом деле, когда shared_ptr привязывается к обычному указателю, мы передаем ответственность за управление памятью этому неизвестному shared_ptr. Следовательно, мы не можем или не должны использовать встроенный указатель для доступа к памяти, на которую указывает shared_ptr.
Праймер также предлагаетНе используйте get для инициализации другого интеллектуального указателя или присвоения значения интеллектуальному указателю.
Выше также кратко описана функция get(), функция которой заключается в том, что она возвращает встроенный указатель на объект, управляемый интеллектуальным указателем. Он предназначен для передачи встроенного указателя в код, который не может использовать интеллектуальные указатели, когда это необходимо. В чем смысл? Это просто управляемый указатель. Давайте посмотрим на этот код

shared_ptr<int> p(new int(42));
int *q = p.get();
{
    // 两个独立的shared_ptr指向相同的内存
    shared_ptr<int>(q);
    // 离开作用域就会释放
}
int foo = *q; // 最后未定义

Итак, здесь объясняется, что вы не можете использовать get() для инициализации другого умного указателя.В конце концов, get() является управляемым.То, что вы даете вам, уже есть, оно просто управляется, и оно дается вам, и вы также указывают на ту же память.
Конечно, shared_ptr также может использовать операцию сброса.

    string *inum = new string("hhh");
    shared_ptr<string> p5 = make_shared<string>("hi");
    p5.reset(inum);

Но его можно использовать только для передачи встроенного указателя.
Также можно передать релизер в shared_ptr.p5.reset(inum,d);
Тогда почему у shared_ptr нет члена выпуска?Нет права собственности. Сказав так много, make_shared, кажется, все время игнорируется.

template <class T,class ... Args>
    shared_ptr<T> make_shared(Args&&... args);

Это его исходный код, его цель - сделатьshared_ptr, возвращаемый типshared_ptr<T>объект, который владеет и хранит указатель на него (счетчик ссылок равен 1).
посмотреть, как использовать

auto baz =make_shared<pair<int,int> > (30,40);
... baz->first .. << baz->second

хорошо, когда мы используемshared_ptrПри инициализации лучше и безопаснее всего использовать эту стандартную библиотечную функцию, а при использовании new необходимо преобразовать, передав право собственности, ноmake_sharedЯ помогу тебе выделить и обезопасить, а что тебе дано, то и взаменshared_ptrТип объекта, на который указывает указатель в строке.
Рекомендуется к использованию!
shared_ptr, по сути, указатель, ставится на релизер, ставится на счетчик, ссылка увеличивается при копировании, присваивание тоже увеличивает ссылку, и счетчик ссылок соответственно декрементируется. Давайте рассмотрим другую ситуацию

struct Node{
    shared_ptr<Node> pPre;
    shared_ptr<Node> pNext;
    int val;
};
void func(){
    shared_ptr<Node> p1(new Node());
    shared_ptr<Node> p2(new Node());
    cout << p1.use_count() << endl;
    cout <<p2.use_count() << endl;
    p1->pNext = p2;
    p2->pPre = p1;
    cout << p1.use_count() << endl;
    cout <<p2.use_count() << endl;
}

Мы видим, что p1 равно 2 и p2 также равно 2. Они копируют и ссылаются друг на друга! Если вы хотите выпустить p2, вы должны сначала выпустить p1, а если вы хотите выпустить p1, вы должны выпустить p2, чтоциклическая ссылкаНаконец, пространство памяти, на которое указывают p1 и p2, никогда не будет освобождено.
Так что мне делать, ничего из вышеперечисленного не решает, не паникуйте, не занимайтесь, просто сидите тихо с обеих сторон.
Спокойно смотри вниз.

weak_ptr пусть Цзинцзин спокойно продолжает, лучше тебя отпустить

Вышеупомянутое кольцо, как нам разорвать это кольцо и освободить память? использоватьweak_ptr. вводитьweak_ptr, интеллектуальный указатель, который не контролирует время жизни объекта, на который указываетshared_ptrуправляемые объекты. Кажется, это еще и удовольствие от долевой собственности, помощи толпы, в отличие отunique_ptr, одинокий человек.
Что значит из-под контроля? то естьweak_ptr, не влияетshared_ptrсчетчик ссылок. однаждыshared_ptrуничтожается, то объект также будет уничтожен, даже еслиweak_ptrТакже указывает на этот объект, который также будет уничтожен. Итак, пришло время отпустить тебя.
Так что это также называется «слабой» совместной собственностью.
Только цитата, не в счет, а нет, проверитьexpired()возник.
Давайте посмотрим на его конструкцию и использование

template <class T> class weak_ptr{
    public:
    constexpr weak_ptr() noexcept;
    weak_ptr(const weak_ptr& x) noexcept;
    template <class U> weak_ptr(const weak_ptr<U>& x) noexcept;
    template <class U> weak_ptr(const shared_ptr<U>& x) noexcept;
}

Итак, как вы можете видеть из конструктора, этоweak_ptr, вы можете построить его самостоятельно или указать наshare_ptr, и только по ссылке.

shared_ptr<int> sp(new int(42));
weak_ptr<int> wp(sp);
cout << wp.use_count << endl;

А как насчет use_count?

long int use_count() const noexcept;

Ты это видел. Это не меняет счетчик ссылок. константа
Что просрочено? Он просто проверяет, стал ли use_count() 0, возвращает false для 0 и возвращает true в противном случае.

bool expired() const noexcept;

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

shared_ptr<T> lock() const noexcept;

lock() проверитweak_ptrСуществует ли указанный объект, и если да, то возвращает shared_ptr.

#include <iostream>
#include <memory>

int main () {
  std::shared_ptr<int> sp1,sp2;
  std::weak_ptr<int> wp;
                                       // sharing group:
                                       // --------------
  sp1 = std::make_shared<int> (20);    // sp1
  wp = sp1;                            // sp1, wp

  sp2 = wp.lock();                     // sp1, wp, sp2
  sp1.reset();                         //      wp, sp2

  sp1 = wp.lock();                     // sp1, wp, sp2

  std::cout << "*sp1: " << *sp1 << '\n';
  std::cout << "*sp2: " << *sp2 << '\n';

  return 0;
}

Ясно, что оба выводят 20. Точно так же сброс может очистить weak_ptr.
Так почему же weak_ptr может разорвать цикл? Продолжим смотреть на следующий код

struct Node{
    weak_ptr<Node> pPre; // 区别⬅️⬅️⬅️
    weak_ptr<Node> pNext; // 区别⬅️⬅️⬅️
    int val;
    Node(){
        cout << "construct" << endl;
    }
    ~Node(){
        cout << "delete" <<endl;
    }
};
void func(){
    shared_ptr<Node> p1(new Node());
    shared_ptr<Node> p2(new Node());
    cout << p1.use_count() << endl;
    cout << p2.use_count() << endl;
    p1->pNext = p2;
    p2->pPre = p1;
    cout << p1.use_count() << endl;
    cout << p2.use_count() << endl;
}

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

Релизеры shared_ptr и unique_ptr демонстрируют свою магическую силу

После разговора о weak_ptr я вдруг почувствовал, что изобретение интеллектуального указателя действительно здорово! Ошибки, которые легко совершить, когда вы одиноки 🐶, уже не так просты. Тогда каждый раз мы будем обнаруживать, что для этих двух указателей будет релизер.
версия unique_ptr

unique_ptr<T,D> up;

версия shared_ptr

shared_ptr<T> p(q,d);

D в любом случае - это удаление, выпуск. Ранее мы представили unique_ptr, который является детерминированным средством удаления, тип которого определяется во время компиляции.
unique_ptr

template<typename T,typename D = default_delete<T> > // default_delete是一个无状态类
class unique_ptr{
    public:
        using pointer = ptr;
        using element_type = T;
        using deleter_type = D;
        ...

Этот shared_ptr

template<typename U>
class shared_ptr{
    public:
        using element_type = U;
        constexpr shared_ptr() noexcept;
        constexpr shared_ptr(nullptr_t):shared_ptr(){} // 空对象
        template <class U> explicit shared_ptr(U* p); // 显式构造 不存在隐式转换
        ...

Когда вы видите этот шаблон, вы можете понять, что у shared_ptr никогда не было фиксированного типа релизера. Хотя по умолчанию используется удаление, вы также можете использовать вызываемый объект. Взгляните на следующий пример вызываемого объекта.

#include <iostream>
#include <memory>

int main () {
   auto deleter = [](Node* p){
    cout << "[deleter called]\n"; 
    delete p;
    };
    // shared_ptr<int> foo (new int,deleter);
    // cout << "use_count: " << foo.use_count() << '\n';
    shared_ptr<Node> bar(new Node(),deleter);
  return 0;                        // [deleter called]
}

Так что релизер, либоunique_ptrвсе ещеshared_ptrдолжен быть сохранен как указатель или класс, который инкапсулирует указатель. Но мы также можем быть уверены, чтоshared_ptrВместо того, чтобы сохранять релизер напрямую как член, поскольку его тип неизвестен до времени выполнения.
потому чтоshared_ptrЕсть только один параметр шаблона, иunique_ptrЕсть два параметра шаблона, поэтому в этомunique_ptrКак это работает, мы видим, что тип этого релизераunique_ptrчастью шрифта, поэтому релизер можно сохранить прямо вunique_ptrв объекте.
Оба релизера либо вызывают предоставленный пользователем релизер по его сохраненному указателю, либо выполняютdelete.
Итак, подводя итог, связывая релизер во время компиляции,unique_ptrИзбегаются накладные расходы во время выполнения на косвенный вызов релизера.
Привязывая релизер во время выполнения,shared_ptrДелает более удобным для пользователя перегрузку релизера.
Итак, это все примеры управления ресурсами с объектами, один за другим.shared_ptr,unique_ptrВсе управляют ресурсами в виде объектов, чтобы предотвратить утечку ресурсов, и динамической памяти больше не нужно бояться утечки.
А как насчет вызываемых объектов? Как это использовать? Почемуshared_ptrМожно ли использовать вызываемые объекты таким образом?
Опубликовал эту статью, надеюсь бэкенд большие коровы невзначай обрызгают, а младший брат обязательно улучшит 😊.
Другой:Писать не просто, при перепечатке указывайте источник

Продолжение следует...