моя контактная информация
Почтамезофия@protonmail.com
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Недостатки необработанных указателей:
delete
Освобождение ресурсов, вызывающее утечку ресурсовdelete
Раньше программа завершалась нормально (например, еслиif
серединаreturn
) или аварийно выйти, пока не стало слишком поздноdelete
, что приводит к утечке ресурсовВ настоящее время необходимы умные указатели. Слово «интеллект» интеллектуальных указателей в основном отражается в том, что пользователям не нужно обращать внимание на освобождение ресурсов, поскольку интеллектуальные указатели помогут вам полностью управлять освобождением ресурсов. Они будут гарантировать, что независимо от того, как работает логика программы. , он будет выполняться нормально или генерировать исключения.Когда ресурсы истечет (область функции или конец программы), они будут освобождены.
Давайте сами реализуем простой умный указатель
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T* ptr = nullptr) : mptr(ptr) {}
~CSmartPtr() { delete mptr; }
private:
T* mptr;
};
int main()
{
CSmartPtr<int> ptr(new int());
/*其它的代码...*/
/*
由于ptr是栈上的智能指针对象,不管是函数正常执行完,
还是运行过程中出现异常,栈上的对象都会自动调用析构函数,
在析构函数中进行了delete操作,保证释放资源
*/
return 0;
}
Следовательно, из-за вышеуказанных характеристик интеллектуальные указатели обычно определяются в стеке. в то же время,Умный указатель — это объект класса , указатель передается в конструкторе этого класса, и переданный указатель освобождается в деструкторе.Поскольку этот тип объекта выделяется и освобождается в стеке, он будет автоматически освобожден, когда наша функция (или программа) завершится.
Итак, можно ли определить интеллектуальные указатели в куче?напримерCSmartPtr* p = new CSmartPtr(new int);
, компиляцию можно пройти, но определение здесьp
Хотя это тип интеллектуального указателя, по сути это необработанный указатель, поэтомуp
Все равно придется делать это вручнуюdelete
, мы возвращаемся к проблеме, с которой мы столкнулись вначале с голыми указателями, поэтому не используйте ее таким образом
Конечно, интеллектуальные указатели должны быть похожи на необработанные указатели, а также обеспечивать общие функции необработанных указателей.*
и->
Перегруженные функции двух операторов при использовании фактически аналогичны необработанным указателям. Код выглядит следующим образом:
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T* ptr = nullptr) : mptr(ptr) {}
~CSmartPtr() { delete mptr; }
T& operator*() { return *mptr; }
T* operator->() { return mptr; }
private:
T* mptr;
};
int main()
{
CSmartPtr<int> ptr(new int());
*ptr = 20; // operator*()一定要返回引用,这样才可以赋值
cout << *ptr << endl; // 20
class Test
{
public:
void test() { cout << "call Test::test()" << endl; }
};
CSmartPtr<Test> ptr2 = new Test();
(*ptr2).test(); // (*ptr2)取出Test对象,用对象调用方法
// operator->()返回的是一个指针,实现了用指针调用函数
// 即(ptr2.operator->()) -> test()
ptr2->test();
return 0;
}
Интеллектуальный указатель, реализованный в предыдущем разделе, очень похож на используемый обычный необработанный указатель, но у него все еще есть большие проблемы. См. следующий код:
CSmartPtr<int> p1(new int());
CSmartPtr<int> p2(p1); // 拷贝构造
Выполняемый код аварийно завершает работу, потому что конструктор копирования по умолчанию выполняет неполную копию.p1
иp2
придерживается того жеnew int
ресурс,p2
Разрушение сначала высвобождает ресурсы, затемp1
При разрушении он становитсяdelete
Дикий указатель, программа вылетает
Итак, как решить проблемы, вызванные поверхностным копированием?переписатьCSmartPtr
Взгляните на конструктор копирования
CSmartPtr(const CSmartPtr<T>& src) { mptr = new T(*src.mptr); }
Теперь код работает нормально, однако на этом этапеp1
иp2
Управляются два разных ресурса, если пользователи их не понимают, они ошибочно подумают, что это так.p1
иp2
Управляйте одним и тем же ресурсом
Поэтому нам неправильно писать конструктор копирования вот так
Итак, как решить проблему поверхностного копирования интеллектуальных указателей? Два метода:Интеллектуальные указатели без подсчета ссылок, интеллектуальные указатели с подсчетом ссылок
Давайте сначала посмотрим на этот разделУмные указатели без подсчета ссылок
auto_ptr
(C++98, сейчас устарел)scoped_ptr
(Библиотека повышения)unique_ptr
(C++11, рекомендуется)Включите файл заголовка при использовании:#include <memory>
Давайте сначала рассмотрим этот код
auto_ptr<int> ptr1(new int());
auto_ptr<int> ptr2(ptr1);
*ptr2 = 20;
cout << *ptr1 << endl;
Операция сорвалась. Почему?auto_ptr
исходный код
Вы можете видеть, что конструкция копирования вызовет входящий объект.release
метод, этот метод состоит в том, чтобы положить_Right
Указанный ресурс возвращается в новыйauto_ptr
объект и в то же время_Right
(старыйauto_ptr
)из_Myptr
установлен вnullptr
Короче говоря, это поставить староеauto_ptr
Указанный ресурс передается новомуauto_ptr
Удерживается, старый установлен наnullptr
.Поэтому в приведенном выше коде используется*ptr1
Просто неправильно.(auto_ptr
Всегда позволяйте последнему умному указателю управлять ресурсом)
Так,auto_ptr
Можно ли использовать его в контейнере? См. следующий код.
int main()
{
vector<auto_ptr<int>> vec;
vec.push_back(auto_ptr<int>(new int(10)));
vec.push_back(auto_ptr<int>(new int(20)));
vec.push_back(auto_ptr<int>(new int(30)));
cout << *vec[0] << endl; // 10
vector<auto_ptr<int>> vec2 = vec;
/* 这里由于上面做了vector容器的拷贝,
相当于容器中的每一个元素都进行了拷贝构造,
原来vec中的智能指针全部为nullptr了,
再次访问就成访问空指针了,程序崩溃
*/
cout << *vec[0] << endl; // 程序崩溃
return 0;
}
Поэтому он устарел в C++.auto_ptr
, если только сценарий применения не очень простой.иauto_ptr
Устарело в C++11 и полностью удалено в C++17.
Подведем итог:auto_ptr
Интеллектуальные указатели не имеют подсчета ссылок, поэтому они решают проблему поверхностного копирования путем прямого копирования предыдущего указателя.auto_ptr
настроены наnullptr
, пусть только последнийauto_ptr
удерживать ресурсы
Вам необходимо установить библиотеку Boost, которая включает в себя файлы заголовков.#include <boost/scoped_ptr.hpp>
Готов использовать
взглянемscoped_ptr
Исходный код:
template<class T> class scoped_ptr
{
private:
scoped_ptr(scoped_ptr const&);
scoped_ptr& operator=(scoped_ptr const&);
...
};
можно увидетьscoped_ptr
Конструктор копирования и функция присваивания приватизированы, поэтому объект не поддерживает эти две операции и не может быть вызван, что принципиально предотвращает возникновение неглубоких копий.
такscoped_ptr
Его нельзя использовать в контейнерах. Если контейнеры копируют или присваивают значения друг другу, это приведет к ошибке.scoped_ptr
Конструктор копирования объекта и функция присваивания, ошибка компиляции
scoped_ptr
иauto_ptr
Разница: Может быть объяснено владением,auto_ptr
Право собственности на ресурсы может передаваться по желанию, при этомscoped_ptr
Право собственности не передается (поскольку конструкторы копирования и функции присваивания отключены)
scoped_ptr
Обычно не используется
сначала посмотриunique_ptr
Часть исходного кода:
template<class _Ty, class _Dx>
class unique_ptr
{
public:
/*提供了右值引用的拷贝构造函数*/
unique_ptr(unique_ptr&& _Right) { ... }
/*提供了右值引用的operator=赋值重载函数*/
unique_ptr& operator=(unique_ptr&& _Right) { ... }
/*
删除了unique_ptr的拷贝构造和赋值函数,
因此不能做unique_ptr智能指针对象的拷贝构造和赋值,
防止浅拷贝的发生
*/
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
};
Если смотреть сверху,unique_ptr
немногоscoped_ptr
Делается то же самое, то есть отключаются перегруженные функции построения копирования и присваивания, а пользователям запрещается использоватьunique_ptr
Выполняйте явное построение копирования и присвоение, чтобы предотвратить возникновение проблем с неглубоким копированием интеллектуальных указателей.
Поскольку конструктор копирования отсутствует,unique_ptr<int> p1(new int()); unique_ptr<int> p2(p1);
Естественно это неправильно
ноunique_ptr
Предоставляет конструкторам копирования и функциям присваивания ссылочные параметры rvalue, то естьunique_ptr
Интеллектуальные указатели могут выполнять операции копирования и присваивания через ссылки rvalue или генерироватьunique_ptr
Место для временных объектов, например.unique_ptr
В качестве возвращаемого значения функции пример кода выглядит следующим образом:
// 示例1
unique_ptr<int> p1(new int());
unique_ptr<int> p2(move(p1)); // 使用了右值引用的拷贝构造
p2 = move(p1); // 使用了右值引用的operator=赋值重载函数
// 示例2
unique_ptr<int> test_uniqueptr()
{
unique_ptr<int> ptr(new int());
return ptr;
}
int main()
{
/*
此处调用test_uniqueptr函数,在return ptr代码处,
调用右值引用的拷贝构造和赋值函数
*/
unique_ptr<int> p = test_uniqueptr(); // 调用带右值引用的拷贝构造函数
p = test_uniqueptr(); // 调用带右值引用的operator=赋值重载函数
return 0;
}
Затем используйтеunique_ptr
Преимущество заключается в том, что пользователи могут использовать что-то вродеunique_ptr<int> p2(move(p1));
высказывание, то ясно видно, чтоp1
Перенесены ресурсы вp2
,p1
Если вы используете, ресурсы больше не сохраняются.auto_ptr
Это не будет явноmove
В написанном виде намерение неочевидно. Если вы не понимаете основной слой, вы будете использовать его неправильно.
В то же время изunique_ptr
Как видно из названия, может быть только один умный указатель, который в конце ссылается на ресурс. Поэтому рекомендуется отдавать приоритет использованию умных указателей без подсчета ссылок.unique_ptr
Интеллектуальные указатели с подсчетом ссылок в основном включают в себяshared_ptr
иweak_ptr
, с подсчетом ссылоквыгодаТо есть несколько интеллектуальных указателей могут управлять одним и тем же ресурсом. Так что же такое интеллектуальные указатели с подсчетом ссылок?
С подсчетом ссылок: Сопоставить счетчик ссылок для каждого ресурса объекта.
Когда нескольким интеллектуальным указателям разрешено указывать на один и тот же ресурс, каждый интеллектуальный указатель добавит 1 к счетчику ссылок на ресурс. Когда интеллектуальный указатель будет уничтожен, он также уменьшит счетчик ссылок на ресурс на 1, так что Последний интеллектуальный указатель будет Когда счетчик ссылок ресурса уменьшится с 1 до 0, это означает, что ресурс может быть освобожден. Деструктор последнего интеллектуального указателя обрабатывает освобождение ресурса. Это концепция подсчета ссылок.
Возьмите то, что было реализовано ранееCSmartPtr
Внесите изменения и загрузите код напрямую:
// 对资源进行引用计数的类
template<typename T>
class RefCnt
{
public:
RefCnt(T* ptr = nullptr) : mptr(ptr)
{
if (mptr != nullptr)
mcount = 1;
}
void addRef() { mcount++; } // 增加资源的引用计数
int delRef() { return --mcount; }
private:
T* mptr;
int mcount;
};
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T* ptr = nullptr) : mptr(ptr)
{
// 智能指针构造的时候给资源建立引用计数对象
mpRefCnt = new RefCnt<T>(mptr);
}
~CSmartPtr()
{
if (0 == mpRefCnt->delRef())
{
delete mptr;
mptr = nullptr;
}
}
// 实现拷贝构造
CSmartPtr(const CSmartPtr<T>& src)
:mptr(src.mptr), mpRefCnt(src.mpRefCnt)
{
if (mptr != nullptr)
mpRefCnt->addRef();
}
CSmartPtr<T>& operator=(const CSmartPtr<T>& src)
{
// 防止自赋值
if (this == &src)
return *this;
// 本身指向的资源减1
// 如果减1为0释放资源;如果减1不为0直接走
if (0 == mpRefCnt->delRef()) { delete mptr; }
mptr = src.mptr;
mpRefCnt = src.mpRefCnt;
mpRefCnt->addRef();
return *this;
}
T& operator*() { return *mptr; }
T* operator->() { return mptr; }
private:
T* mptr; // 指向资源的指针
RefCnt<T>* mpRefCnt; // 指向该资源引用计数对象的指针
};
// 那么现在就不会报错了,不会对同一个资源释放多次
CSmartPtr<int> ptr1(new int());
CSmartPtr<int> ptr2(ptr1);
CSmartPtr<int> ptr3;
ptr3 = ptr2;
*ptr1 = 20;
cout << *ptr2 << " " << *ptr3 << endl; // 20 20
Это позволяет нескольким интеллектуальным указателям управлять одним и тем же ресурсом.
Однако интеллектуальные указатели, которые мы реализуем сейчас, не являются потокобезопасными и не могут использоваться в многопоточных сценариях.Реализовано Карриshared_ptr
иweak_ptr
Это потокобезопасно!
shared_ptr
:мощныйИнтеллектуальные указатели (могут изменять количество ссылок на ресурсы)weak_ptr
:слабыйИнтеллектуальные указатели (не меняйте счетчик ссылок ресурса)Что мы реализовали в предыдущем разделеCSmartPtr
Также сильный умный указатель (может изменить количество ссылок на ресурс).
Это можно понять так: Слабый умный указатель наблюдать Сильные умные указатели Сильные умные указателинаблюдать Ресурсы (память)
Так,Сильная проблема с перекрестными ссылками (циклическими ссылками) интеллектуальных указателей Что тогда?Приходите и посмотрите
class B; // 前置声明类B
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
shared_ptr<B> _ptrb; // 指向B对象的智能指针
};
class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
shared_ptr<A> _ptra; // 指向A对象的智能指针
};
int main()
{
shared_ptr<A> pa(new A()); // pa指向A对象,A的引用计数为1
shared_ptr<B> pb(new B()); // pb指向B对象,B的引用计数为1
pa->_ptrb = pb; // A对象的成员变量_ptrb也指向B对象,B的引用计数为2
pb->_ptra = pa; // B对象的成员变量_ptra也指向A对象,A的引用计数为2
cout << pa.use_count() << endl;
cout << pb.use_count() << endl;
return 0;
}
результат операции:
A()
B()
2
2
Как видите, A и B не уничтожаются, а это значит, что перекрестные ссылки вызовутnew
Выходящие ресурсы не могут быть освобождены, что приводит к утечке ресурсов!
анализировать:
внеmain
объем функции,pa
иpb
Два локальных объекта уничтожаются, а счетчики ссылок объекта A и объекта B уменьшаются с 2 до 1 соответственно. Условия освобождения A и B не могут быть выполнены (условием освобождения является уменьшение счетчиков ссылок A и B). до 0), что приводит к появлению двухnew
Вышедшие объекты A и B не могут быть освобождены, что приводит к утечкам памяти. Это проблема.Сильная проблема с перекрестными ссылками (циклическими ссылками) интеллектуальных указателей
Решение. Используйте сильные интеллектуальные указатели при определении объектов и слабые интеллектуальные указатели при ссылках на объекты.
class B; // 前置声明类B
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
weak_ptr<B> _ptrb; // 指向B对象的弱智能指针(引用对象时,用弱智能指针)
};
class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
weak_ptr<A> _ptra; // 指向A对象的弱智能指针(引用对象时,用弱智能指针)
};
int main()
{
// 定义对象时,用强智能指针
shared_ptr<A> pa(new A()); // pa指向A对象,A的引用计数为1
shared_ptr<B> pb(new B()); // pb指向B对象,B的引用计数为1
// A对象的成员变量_ptrb也指向B对象,B的引用计数为1,因为是弱智能指针,引用计数没有改变
pa->_ptrb = pb;
// B对象的成员变量_ptra也指向A对象,A的引用计数为1,因为是弱智能指针,引用计数没有改变
pb->_ptra = pa;
cout << pa.use_count() << endl; // 1
cout << pb.use_count() << endl; // 1
return 0;
}
результат операции:
A()
B()
1
1
~B()
~A()
можно увидеть, внеmain
объем функции,pa
иpb
Два локальных объекта уничтожаются, а счетчики ссылок объекта A и объекта B соответственно уменьшаются с 1 до 0, достигая условий для освобождения A и B. Следовательно,new
Вышедшие объекты A и B были уничтожены, что решило проблему.Сильная проблема с перекрестными ссылками (циклическими ссылками) интеллектуальных указателей
можно увидеть,weak_ptr
Слабые интеллектуальные указатели не изменяют счетчик ссылок на ресурс. Это означает, что слабый интеллектуальный указатель только проверяет, жив ли объект (если счетчик ссылок равен 0), и не может использовать ресурс.
Тогда если в это время добавить в класс Аvoid testA() { cout << "非常好用的方法!!" << endl; }
Такой метод добавляет к Bvoid func() { _ptra->testA(); }
Можно ли вызвать этот метод?
*
и->
Перегруженные функции операторов не могут использовать функции, подобные необработанным указателям.Решение:
class B; // 前置声明类B
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void testA() { cout << "非常好用的方法!!" << endl; }
weak_ptr<B> _ptrb; // 指向B对象的弱智能指针(引用对象时,用弱智能指针)
};
class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
void func()
{
shared_ptr<A> sp = _ptra.lock(); // 提升方法,出函数作用域就自动析构了
if (sp != nullptr)
sp->testA();
}
weak_ptr<A> _ptra; // 指向A对象的弱智能指针(引用对象时,用弱智能指针)
};
int main()
{
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
pa->_ptrb = pb;
pb->_ptra = pa;
cout << pa.use_count() << endl; // 1
cout << pb.use_count() << endl; // 1
pb->func();
return 0;
}
Хотя
weak_ptr
не владеет объектом, но может пройтиlock()
метод пытается получить указатель на объектshared_ptr
, если объект еще жив (т.е. есть другиеshared_ptr
укажите на это),lock()
вернет указатель на объектshared_ptr
; В противном случае будет возвращено пустое значение.shared_ptr
использовать
lock()
Типичный сценарий для методов — когда требуется временный доступ.weak_ptr
объект, на который указывает, и в то же время не хотят увеличивать время жизни объекта
результат операции:
A()
B()
1
1
非常好用的方法!!
~B()
~A()
На этом этапе вы можете видеть, что оно было названо правильно!
Давайте посмотрим одинПроблемы безопасности потоков при доступе к общим объектам из нескольких потоков: поток A и поток B получают доступ к общему объекту. Если поток A уничтожает объект, поток B должен вызвать метод-член общего объекта. В это время поток A может завершить разрушение объекта, а поток B не будет. Если вы попытаетесь получить доступ к объекту, произойдет непредвиденная ошибка.
Сначала посмотрите на следующий код:
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void testA() { cout << "非常好用的方法!!" << endl; }
};
// 子线程
void handler01(A* q)
{
// 睡眠两秒,此时main主线程已经把A对象给delete析构掉了
std::this_thread::sleep_for(std::chrono::seconds(2));
q->testA();
}
// main线程
int main()
{
A* p = new A();
thread t1(handler01, p);
delete p;
//阻塞当前线程,直到调用join()的线程结束
t1.join();
return 0;
}
в исполненииq->testA();
Когда делается это заявление,main
Общий объект был уничтожен потоком, что явно неразумно.
Если вы хотите пройтиq
Если указатель хочет получить доступ к объекту A, ему необходимо определить, активен ли объект A. Если объект A активен, вызовите.testA
С методом нет проблем; если объект A был уничтожен, вызовите его.testA
есть проблема!То естьq
При доступе к объекту A вам необходимо определить, жив ли объект A. Как решить эту проблему? Вам нужно использовать сильные и слабые интеллектуальные указатели.
Давайте посмотрим на этот код:
// 子线程
void handler01(weak_ptr<A> wp)
{
// 睡眠两秒
std::this_thread::sleep_for(std::chrono::seconds(2));
shared_ptr<A> sp = wp.lock();
if (sp != nullptr)
sp->testA();
else
cout << "A对象已经析构,不能再访问!" << endl;
}
// main线程
int main()
{
shared_ptr<A> p(new A());
thread t1(handler01, weak_ptr<A>(p));
t1.join();
return 0;
}
результат операции:
A()
非常好用的方法!!
~A()
Вы можете видеть, что он работаетsp->testA();
,потому чтоmain
Тред под названиемt1.join()
В это время метод ожидает завершения дочернего потока.wp
проходитьlock
Успешно повышен доsp
Измените приведенный выше код:
// 子线程
void handler01(weak_ptr<A> wp)
{
// 睡眠两秒
std::this_thread::sleep_for(std::chrono::seconds(2));
shared_ptr<A> sp = wp.lock();
if (sp != nullptr)
sp->testA();
else
cout << "A对象已经析构,不能再访问!" << endl;
}
// main线程
int main()
{
{
shared_ptr<A> p(new A());
thread t1(handler01, weak_ptr<A>(p));
t1.detach();
}
std::this_thread::sleep_for(std::chrono::seconds(3));
return 0;
}
результат операции:
A()
~A()
A对象已经析构,不能再访问!
Как видите, мы установили область действия, а также установилиt1
Чтобы разделить потоки, позвольте умным указателямp
Когда A уничтожается вне области видимости, он будет напечатан в это время.A对象已经析构,不能再访问!
, то естьwp
проходитьlock
Не удалось успешно обновиться доsp
Вышеупомянутая проблема связана с безопасностью потоков при многопоточном доступе к общим объектам. Это ссылка на.shared_ptr
иweak_ptr
Типичное приложение.
Справочная статья:Глубокое понимание интеллектуальных указателей C++.