Обмен технологиями

Испытайте силу умных указателей

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina


Каталог курсов



1. Базовые знания умных указателей.

Недостатки необработанных указателей:

  1. забыватьdeleteОсвобождение ресурсов, вызывающее утечку ресурсов
  2. существоватьdeleteРаньше программа завершалась нормально (например, еслиifсерединаreturn) или аварийно выйти, пока не стало слишком поздноdelete, что приводит к утечке ресурсов
  3. Один и тот же ресурс высвобождается несколько раз, что приводит к освобождению диких указателей и сбою программы.

В настоящее время необходимы умные указатели. Слово «интеллект» интеллектуальных указателей в основном отражается в том, что пользователям не нужно обращать внимание на освобождение ресурсов, поскольку интеллектуальные указатели помогут вам полностью управлять освобождением ресурсов. Они будут гарантировать, что независимо от того, как работает логика программы. , он будет выполняться нормально или генерировать исключения.Когда ресурсы истечет (область функции или конец программы), они будут освобождены.

Давайте сами реализуем простой умный указатель

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  1. Интеллектуальные указатели воплощаются в объектно-ориентированной инкапсуляции необработанных указателей, инициализации адресов ресурсов в конструкторе и освобождении ресурсов в деструкторе.
  2. Благодаря функции автоматического уничтожения объектов в стеке ресурсы гарантированно освобождаются в деструкторе интеллектуального указателя.

Следовательно, из-за вышеуказанных характеристик интеллектуальные указатели обычно определяются в стеке. в то же время,Умный указатель — это объект класса , указатель передается в конструкторе этого класса, и переданный указатель освобождается в деструкторе.Поскольку этот тип объекта выделяется и освобождается в стеке, он будет автоматически освобожден, когда наша функция (или программа) завершится.

Итак, можно ли определить интеллектуальные указатели в куче?например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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

2. Умные указатели без подсчета ссылок

Интеллектуальный указатель, реализованный в предыдущем разделе, очень похож на используемый обычный необработанный указатель, но у него все еще есть большие проблемы. См. следующий код:

CSmartPtr<int> p1(new int());
CSmartPtr<int> p2(p1);	// 拷贝构造
  • 1
  • 2

Выполняемый код аварийно завершает работу, потому что конструктор копирования по умолчанию выполняет неполную копию.p1иp2придерживается того жеnew intресурс,p2Разрушение сначала высвобождает ресурсы, затемp1При разрушении он становитсяdeleteДикий указатель, программа вылетает

Итак, как решить проблемы, вызванные поверхностным копированием?переписатьCSmartPtrВзгляните на конструктор копирования

CSmartPtr(const CSmartPtr<T>& src) { mptr = new T(*src.mptr); }
  • 1

Теперь код работает нормально, однако на этом этапеp1иp2Управляются два разных ресурса, если пользователи их не понимают, они ошибочно подумают, что это так.p1иp2Управляйте одним и тем же ресурсом

Поэтому нам неправильно писать конструктор копирования вот так

Итак, как решить проблему поверхностного копирования интеллектуальных указателей? Два метода:Интеллектуальные указатели без подсчета ссылок, интеллектуальные указатели с подсчетом ссылок

Давайте сначала посмотрим на этот разделУмные указатели без подсчета ссылок

  1. auto_ptr(C++98, сейчас устарел)
  2. scoped_ptr(Библиотека повышения)
  3. unique_ptr(C++11, рекомендуется)

Включите файл заголовка при использовании:#include <memory>

auto_ptr

Давайте сначала рассмотрим этот код

auto_ptr<int> ptr1(new int());
auto_ptr<int> ptr2(ptr1);

*ptr2 = 20;
cout << *ptr1 << endl;
  • 1
  • 2
  • 3
  • 4
  • 5

Операция сорвалась. Почему?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;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

Поэтому он устарел в C++.auto_ptr , если только сценарий применения не очень простой.иauto_ptrУстарело в C++11 и полностью удалено в C++17.

Подведем итог:auto_ptrИнтеллектуальные указатели не имеют подсчета ссылок, поэтому они решают проблему поверхностного копирования путем прямого копирования предыдущего указателя.auto_ptrнастроены наnullptr, пусть только последнийauto_ptrудерживать ресурсы

scoped_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&);
...
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

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

scoped_ptrиauto_ptrРазница: Может быть объяснено владением,auto_ptrПраво собственности на ресурсы может передаваться по желанию, при этомscoped_ptrПраво собственности не передается (поскольку конструкторы копирования и функции присваивания отключены)

scoped_ptrОбычно не используется

unique_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;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

Если смотреть сверху,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=赋值重载函数
  • 1
  • 2
  • 3
  • 4
// 示例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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

Затем используйтеunique_ptrПреимущество заключается в том, что пользователи могут использовать что-то вродеunique_ptr<int> p2(move(p1));высказывание, то ясно видно, чтоp1Перенесены ресурсы вp2p1Если вы используете, ресурсы больше не сохраняются.auto_ptrЭто не будет явноmoveВ написанном виде намерение неочевидно. Если вы не понимаете основной слой, вы будете использовать его неправильно.

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

3. Умные указатели с подсчетом ссылок

Интеллектуальные указатели с подсчетом ссылок в основном включают в себяshared_ptrиweak_ptr, с подсчетом ссылоквыгодаТо есть несколько интеллектуальных указателей могут управлять одним и тем же ресурсом. Так что же такое интеллектуальные указатели с подсчетом ссылок?

С подсчетом ссылок: Сопоставить счетчик ссылок для каждого ресурса объекта.

Когда нескольким интеллектуальным указателям разрешено указывать на один и тот же ресурс, каждый интеллектуальный указатель добавит 1 к счетчику ссылок на ресурс. Когда интеллектуальный указатель будет уничтожен, он также уменьшит счетчик ссылок на ресурс на 1, так что Последний интеллектуальный указатель будет Когда счетчик ссылок ресурса уменьшится с 1 до 0, это означает, что ресурс может быть освобожден. Деструктор последнего интеллектуального указателя обрабатывает освобождение ресурса. Это концепция подсчета ссылок.

  • При подсчете ссылокминус 1 это не 0Когда текущий интеллектуальный указатель больше не использует этот ресурс, но есть другие интеллектуальные указатели, использующие этот ресурс. Текущий интеллектуальный указатель не может уничтожить этот ресурс и может идти только напрямую.
  • При подсчете ссылокУменьшить с 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;	// 指向该资源引用计数对象的指针
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
// 那么现在就不会报错了,不会对同一个资源释放多次
CSmartPtr<int> ptr1(new int());
CSmartPtr<int> ptr2(ptr1);
CSmartPtr<int> ptr3;
ptr3 = ptr2;

*ptr1 = 20;
cout << *ptr2 << " " << *ptr3 << endl;	// 20 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Это позволяет нескольким интеллектуальным указателям управлять одним и тем же ресурсом.

Однако интеллектуальные указатели, которые мы реализуем сейчас, не являются потокобезопасными и не могут использоваться в многопоточных сценариях.Реализовано Карриshared_ptrиweak_ptrЭто потокобезопасно!

проблема с перекрестной ссылкойshared_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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

результат операции:

A()
B()
2
2
  • 1
  • 2
  • 3
  • 4

Как видите, 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

результат операции:

A()
B()
1
1
~B()
~A()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

можно увидеть, вне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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

Хотя weak_ptrне владеет объектом, но может пройтиlock()метод пытается получить указатель на объектshared_ptr, если объект еще жив (т.е. есть другиеshared_ptrукажите на это),lock()вернет указатель на объектshared_ptr; В противном случае будет возвращено пустое значение.shared_ptr

использоватьlock()Типичный сценарий для методов — когда требуется временный доступ.weak_ptrобъект, на который указывает, и в то же время не хотят увеличивать время жизни объекта

результат операции:

A()
B()
1
1
非常好用的方法!!
~B()
~A()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

На этом этапе вы можете видеть, что оно было названо правильно!

4. Проблемы безопасности потоков при многопоточном доступе к общим объектам.

Давайте посмотрим одинПроблемы безопасности потоков при доступе к общим объектам из нескольких потоков: поток 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

в исполнении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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

результат операции:

A()
非常好用的方法!!
~A()
  • 1
  • 2
  • 3

Вы можете видеть, что он работает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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

результат операции:

A()
~A()
A对象已经析构,不能再访问!
  • 1
  • 2
  • 3

Как видите, мы установили область действия, а также установилиt1Чтобы разделить потоки, позвольте умным указателямpКогда A уничтожается вне области видимости, он будет напечатан в это время.A对象已经析构,不能再访问!, то естьwpпроходитьlockНе удалось успешно обновиться доsp

Вышеупомянутая проблема связана с безопасностью потоков при многопоточном доступе к общим объектам. Это ссылка на.shared_ptrиweak_ptrТипичное приложение.

5. Умное удаление указателя

6. Рекомендуется использовать make_shared вместоshared_ptr.


Справочная статья:Глубокое понимание интеллектуальных указателей C++.