Technologieaustausch

Erleben Sie die Kraft intelligenter Zeiger

2024-07-12

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


Kurs Katalog



1. Grundkenntnisse über Smart Pointer

Nachteile von Rohzeigern:

  1. vergessendeleteGeben Sie Ressourcen frei, was zu einem Ressourcenverlust führt
  2. existierendeleteDas Programm wurde zuvor normal beendet (z. B. wennifMittereturn) oder abnormal beenden, bevor es zu spät istdelete, was zu einem Ressourcenverlust führt
  3. Dieselbe Ressource wird mehrmals freigegeben, was dazu führt, dass wilde Zeiger freigegeben werden und das Programm abstürzt.

Zu diesem Zeitpunkt sind intelligente Hinweise erforderlich. Die Intelligenz intelligenter Zeiger spiegelt sich hauptsächlich in der Tatsache wider, dass Benutzer nicht auf die Freigabe von Ressourcen achten müssen, da intelligente Zeiger Ihnen dabei helfen, die Freigabe von Ressourcen vollständig zu verwalten. Dies stellt sicher, dass die Programmlogik unabhängig davon ausgeführt wird , wird es normal ausgeführt oder es werden Ausnahmen generiert.Wenn Ressourcen ablaufen (Funktionsumfang oder Programmende), werden sie freigegeben.

Lassen Sie uns selbst einen einfachen Smart Pointer implementieren

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. Intelligente Zeiger sind in der objektorientierten Kapselung von Rohzeigern, der Initialisierung von Ressourcenadressen im Konstruktor und der Freigabe von Ressourcen im Destruktor verkörpert.
  2. Durch die Nutzung der Funktion der automatischen Bereichszerstörung von Objekten auf dem Stapel wird garantiert, dass Ressourcen im Destruktor des Smart Pointers freigegeben werden.

Aufgrund der oben genannten Eigenschaften werden intelligente Zeiger daher im Allgemeinen auf dem Stapel definiert. gleichzeitig,Ein Smart Pointer ist ein Klassenobjekt , wird im Konstruktor dieser Klasse ein Zeiger übergeben und der übergebene Zeiger im Destruktor freigegeben.Da dieser Objekttyp auf dem Stapel zugewiesen und freigegeben wird, wird er automatisch freigegeben, wenn unsere Funktion (oder unser Programm) endet.

Können also intelligente Zeiger auf dem Heap definiert werden?Zum BeispielCSmartPtr* p = new CSmartPtr(new int);, die Kompilierung kann bestehen, aber die Definition hierpObwohl es sich um einen intelligenten Zeigertyp handelt, handelt es sich im Wesentlichen um einen RohzeigerpMuss es immer noch manuell machendelete, es ist wieder das Problem, mit dem wir am Anfang mit bloßen Zeigern konfrontiert waren, also verwenden Sie es nicht so

Natürlich müssen intelligente Zeiger Rohzeigern ähnlich sein und auch gemeinsame Merkmale von Rohzeigern bieten.*Und->Die überladenen Funktionen der beiden Operatoren sind bei Verwendung tatsächlich dieselben wie Rohzeiger. Der Code lautet wie folgt:

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. Intelligente Zeiger ohne Referenzzählung

Der im vorherigen Abschnitt implementierte Smart Pointer ist einem gewöhnlichen Rohzeiger sehr ähnlich, weist jedoch immer noch große Probleme auf. Siehe den folgenden Code:

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

Der laufende Code stürzt direkt ab, da der Standardkopierkonstruktor eine flache Kopie durchführt.p1Undp2gilt das Gleichenew intRessource,p2Die Zerstörung gibt dann zunächst die Ressourcen freip1Wenn es zerstört wird, wird esdeleteWilder Zeiger, Programm stürzt ab

Wie können also die durch flache Kopien verursachten Probleme gelöst werden?umschreibenCSmartPtrSchauen Sie sich den Kopierkonstruktor an

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

Jetzt läuft der Code jedoch an dieser Stelle normalp1Undp2Es werden zwei unterschiedliche Ressourcen verwaltet. Wenn Benutzer sie nicht verstehen, denken sie fälschlicherweisep1Undp2Verwalten Sie dieselbe Ressource

Daher ist es falsch, den Kopierkonstruktor so zu schreiben

Wie kann man also das Problem der flachen Kopie intelligenter Zeiger lösen? Zwei Methoden:Intelligente Zeiger ohne Referenzzählung, intelligente Zeiger mit Referenzzählung

Schauen wir uns zunächst diesen Abschnitt anIntelligente Zeiger ohne Referenzzählung

  1. auto_ptr(C++98, jetzt veraltet)
  2. scoped_ptr(Boost-Bibliothek)
  3. unique_ptr(C++11, empfohlen)

Schließen Sie die Header-Datei ein, wenn Sie Folgendes verwenden:#include <memory>

auto_ptr

Betrachten wir zunächst diesen Code

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

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

Warum ist die Operation abgestürzt?auto_ptrQuellcode

Fügen Sie hier eine Bildbeschreibung ein

Sie können sehen, dass die Kopierkonstruktion das eingehende Objekt aufruftreleaseMethode, diese Methode ist zu setzen_RightDie Ressource, auf die verwiesen wird, wird an die neue zurückgegebenauto_ptrObjekt und gleichzeitig_Right(altauto_ptr)von_Myptreinstellennullptr

Kurz gesagt, es geht darum, das Alte zu setzenauto_ptrDie Ressource, auf die verwiesen wird, wird dem Neuen übergebenauto_ptrGehalten, das alte ist eingestelltnullptr .Daher wird der obige Code verwendet*ptr1Einfach falsch.auto_ptrLassen Sie immer den letzten Smart Pointer die Ressource verwalten)

Also,auto_ptrKann es in einem Container verwendet werden? Siehe den folgenden Code.

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

Daher ist es in C++ veraltetauto_ptr , es sei denn, das Anwendungsszenario ist sehr einfach.Undauto_ptrIn C++11 veraltet und in C++17 vollständig entfernt

Zusammenfassen:auto_ptrIntelligente Zeiger verfügen nicht über eine Referenzzählung, sodass sie das Problem des flachen Kopierens lösen, indem sie das Vorherige direkt kopierenauto_ptreingestellt sindnullptr, lass nur das Letzteauto_ptrRessourcen halten

scoped_ptr

Sie müssen die Boost-Bibliothek installieren, die die Header-Dateien enthält.#include <boost/scoped_ptr.hpp>Einsatzbereit

Schau malscoped_ptrQuellcode:

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

kann gesehen werdenscoped_ptrSowohl der Kopierkonstruktor als auch die Zuweisungsfunktion sind privatisiert, sodass das Objekt diese beiden Vorgänge nicht unterstützt und nicht aufgerufen werden kann, was das Auftreten flacher Kopien grundsätzlich verhindert.

Alsoscoped_ptrEs kann nicht in Containern verwendet werden, wenn Container einander Werte kopieren oder zuweisenscoped_ptrObjektkopiekonstruktor und Zuweisungsfunktion, Kompilierungsfehler

scoped_ptrUndauto_ptrDer Unterschied: Kann durch Besitz erklärt werden,auto_ptrDas Eigentum an Ressourcen kann nach Belieben übertragen werdenscoped_ptrDas Eigentum wird nicht übertragen (da Kopierkonstruktoren und Zuweisungsfunktionen deaktiviert sind)

scoped_ptrIm Allgemeinen nicht verwendet

einzigartiger_ptr

Schauen Sie sich das erst einmal anunique_ptrTeil des Quellcodes:

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

Von oben gesehen,unique_ptrein bisschenscoped_ptrDasselbe geschieht, d. h. die überlasteten Kopierkonstruktions- und Zuweisungsfunktionen werden deaktiviert und Benutzern ist die Verwendung untersagtunique_ptrFühren Sie eine explizite Kopierkonstruktion und -zuweisung durch, um das Auftreten von Smart-Pointer-Flachkopieproblemen zu verhindern.

Da es keinen Kopierkonstruktor gibt,unique_ptr<int> p1(new int()); unique_ptr<int> p2(p1);Natürlich ist es falsch

Aberunique_ptrStellt Kopierkonstruktoren und Zuweisungsfunktionen mit R-Wert-Referenzparametern bereit, d. h.unique_ptrIntelligente Zeiger können Kopierkonstruktions- und Zuweisungsvorgänge über R-Wert-Referenzen ausführen oder generierenunique_ptrEin Ort für temporäre Gegenstände, z.Bunique_ptrAls Rückgabewert der Funktion lautet der Beispielcode wie folgt:

// 示例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

Dann benutzeunique_ptrDer Vorteil besteht darin, dass Benutzer so etwas verwenden könnenunique_ptr<int> p2(move(p1));Aussage, das ist deutlich zu erkennenp1Übertragene Ressourcen anp2p1Bei Verwendung werden keine weiteren Ressourcen gespeichertauto_ptrEs wird nicht explizit seinmoveAusgeschrieben ist die Absicht nicht offensichtlich. Wenn Sie die zugrunde liegende Ebene nicht verstehen, werden Sie sie falsch verwenden.

Zur gleichen Zeit, vonunique_ptrWie aus dem Namen hervorgeht, kann es am Ende nur einen Smart Pointer geben, der auf eine Ressource verweist. Daher wird empfohlen, der Verwendung von Smart Pointern ohne Referenzzählung Vorrang einzuräumen.unique_ptr

3. Intelligente Zeiger mit Referenzzählung

Intelligente Zeiger mit Referenzzählung umfassen hauptsächlichshared_ptrUndweak_ptr, mit ReferenzzählungNutzenDas heißt, mehrere Smart Pointer können dieselbe Ressource verwalten. Was sind Smart Pointer mit Referenzzählung?

Mit Referenzzählung: Passen Sie einen Referenzzähler für die Ressource jedes Objekts an.

Wenn mehrere intelligente Zeiger auf dieselbe Ressource verweisen dürfen, erhöht jeder intelligente Zeiger den Referenzzähler der Ressource um 1. Wenn ein intelligenter Zeiger zerstört wird, verringert er auch den Referenzzähler der Ressource um 1 Wenn der Referenzzähler einer Ressource von 1 auf 0 sinkt, bedeutet dies, dass die Ressource freigegeben werden kann. Der Destruktor des letzten Smart Pointers übernimmt die Freigabe der Ressource.

  • Beim Referenzzählenminus 1 ist nicht 0Wenn der aktuelle Smart Pointer diese Ressource nicht mehr verwendet, es jedoch andere Smart Pointer gibt, die diese Ressource verwenden, kann der aktuelle Smart Pointer diese Ressource nicht zerstören und nur direkt darauf zugreifen
  • Beim ReferenzzählenVon 1 auf 0 verringernWenn dies der Fall ist, bedeutet dies, dass der aktuelle Smart Pointer der letzte Smart Pointer ist, der diese Ressource verwendet, und daher für die Freigabe dieser Ressource verantwortlich ist.

Simulieren Sie die Implementierung eines Smart Pointers mit Referenzzählung

Nehmen Sie, was zuvor implementiert wurdeCSmartPtrNehmen Sie Änderungen vor und laden Sie den Code direkt hoch:

// 对资源进行引用计数的类
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

Dadurch können mehrere Smart Pointer dieselbe Ressource verwalten.

Allerdings sind die intelligenten Zeiger, die wir jetzt implementieren, nicht threadsicher und können nicht in Multithread-Szenarien verwendet werden.Umgesetzt von Curryshared_ptrUndweak_ptrEs ist threadsicher!

Problem mit dem Querverweis zu shared_ptr

  • shared_ptrmächtigIntelligente Zeiger (können die Referenzanzahl von Ressourcen ändern)
  • weak_ptrschwachIntelligente Zeiger (verändern den Referenzzähler der Ressource nicht)

Was wir im vorherigen Abschnitt implementiert habenCSmartPtrAuch ein starker intelligenter Zeiger (kann den Referenzzähler der Ressource ändern)

Es kann so verstanden werden: Schwacher intelligenter Zeiger beobachten Starke intelligente Zeiger Starke intelligente Zeigerbeobachten Ressourcen (Speicher)

Also,Starkes Problem mit Smart-Pointer-Querverweisen (Zirkelverweisen). was ist es dann?Kommen Sie vorbei und schauen Sie es sich an

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

Operationsergebnis:

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

Wie Sie sehen können, werden A und B nicht zerstört, was bedeutet, dass es zu Querverweisen kommtnewDie austretenden Ressourcen können nicht freigegeben werden, was zu einem Ressourcenverlust führt!

analysieren:
Fügen Sie hier eine Bildbeschreibung einFügen Sie hier eine Bildbeschreibung ein
ausmainFunktionsumfang,paUndpbZwei lokale Objekte werden zerstört und die Referenzanzahl von Objekt A und Objekt B wird von 2 auf 1 reduziert. Die Bedingungen für die Freigabe von A und B können nicht erfüllt werden (die Bedingung für die Freigabe ist, dass die Referenzanzahl von A und B reduziert wird). auf 0), wodurch zwei verursacht werdennewDie ausgegebenen A- und B-Objekte können nicht freigegeben werden, was zu Speicherverlusten führtStarkes Problem mit Smart-Pointer-Querverweisen (Zirkelverweisen).

Lösung: Verwenden Sie beim Definieren von Objekten starke intelligente Zeiger und beim Referenzieren von Objekten schwache intelligente Zeiger.

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

Operationsergebnis:

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

kann gesehen werden, rausmainFunktionsumfang,paUndpbDie beiden lokalen Objekte werden zerstört und die Referenzanzahl von Objekt A und Objekt B wird von 1 auf 0 reduziert, wodurch die Bedingungen für die Freigabe von A und B erreicht werden. Daher gilt:newDas A-Objekt und das B-Objekt, die herauskamen, wurden zerstört, wodurch das Problem gelöst wurde.Starkes Problem mit Smart-Pointer-Querverweisen (Zirkelverweisen).

Fügen Sie hier eine Bildbeschreibung ein
kann gesehen werden,weak_ptrSchwache intelligente Zeiger ändern den Referenzzähler der Ressource nicht, was bedeutet, dass schwache intelligente Zeiger nur beobachten, ob das Objekt aktiv ist (ob der Referenzzähler 0 ist), und die Ressource nicht verwenden können.

Fügen Sie dann, wenn zu diesem Zeitpunkt, Klasse A hinzuvoid testA() { cout << "非常好用的方法!!" << endl; }Eine solche Methode ergänzt Bvoid func() { _ptra->testA(); }Ist es in Ordnung, diese Methode aufzurufen?

  • Kippen, weil der schwache Smart Pointer nur ein Beobachter ist und keine Ressourcen nutzen kann, das heißt, er stellt keine bereit*Und->Überladene Funktionen von Operatoren können keine Funktionen verwenden, die Rohzeigern ähneln

Lösung:

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

Obwohl weak_ptrbesitzt das Objekt nicht, kann es aber passierenlock()Die Methode versucht, einen Zeiger auf das Objekt zu erhaltenshared_ptr, wenn das Objekt noch lebt (d. h. es andere gibt).shared_ptrZeige auf es),lock()gibt einen Zeiger auf das Objekt zurückshared_ptr; Andernfalls wird ein leerer Wert zurückgegebenshared_ptr

verwendenlock()Ein typisches Szenario für Methoden ist, wenn temporärer Zugriff erforderlich istweak_ptrdas Objekt, auf das gezeigt wird, und möchten gleichzeitig die Lebensdauer des Objekts nicht erhöhen

Operationsergebnis:

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

An dieser Stelle erkennt man, dass es richtig aufgerufen wurde!

4. Thread-Sicherheitsprobleme, wenn Multithreads auf gemeinsam genutzte Objekte zugreifen

Schauen wir uns eins anThread-Sicherheitsprobleme beim Zugriff auf gemeinsam genutzte Objekte von mehreren Threads aus: Thread A und Thread B greifen auf ein gemeinsam genutztes Objekt zu, Thread B muss die Member-Methode des gemeinsam genutzten Objekts aufrufen. Zu diesem Zeitpunkt hat Thread A möglicherweise die Zerstörung des Objekts abgeschlossen, Thread B jedoch nicht Wenn Sie versuchen, auf das Objekt zuzugreifen, tritt ein unerwarteter Fehler auf.

Schauen Sie sich zunächst den folgenden Code an:

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

in der Ausführungq->testA();Wenn diese Aussage gemacht wird, wird diemainDas gemeinsam genutzte Objekt wurde vom Thread zerstört, was offensichtlich unvernünftig ist.

Wenn du bestehen willstqWenn der Zeiger auf das A-Objekt zugreifen möchte, muss er feststellen, ob das A-Objekt aktiv ist. Wenn das A-Objekt aktiv ist, rufen Sie auftestAEs gibt kein Problem mit der Methode; wenn das A-Objekt zerstört wurde, rufen Sie es auftestA hat ein Problem!Das heißtqBeim Zugriff auf Objekt A müssen Sie feststellen, ob Objekt A aktiv ist. Wie lässt sich dieses Problem lösen? Sie müssen starke und schwache intelligente Zeiger verwenden.

Schauen wir uns diesen Code an:

// 子线程
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

Operationsergebnis:

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

Man sieht, dass es läuftsp->testA();,WeilmainDer Thread hat aufgerufent1.join()Die Methode wartet auf das Ende des untergeordneten ThreadswppassierenlockErfolgreich befördertsp

Ändern Sie den obigen Code:

// 子线程
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

Operationsergebnis:

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

Wie Sie sehen, legen wir einen Umfang fest und legen auch festt1Um Threads zu trennen, verwenden Sie intelligente ZeigerpWenn A außerhalb des Gültigkeitsbereichs zerstört wird, wird es zu diesem Zeitpunkt gedrucktA对象已经析构,不能再访问!, das istwppassierenlockDas Upgrade auf konnte nicht erfolgreich durchgeführt werdensp

Das Obige ist das Thread-Sicherheitsproblem, wenn Multithreads auf gemeinsam genutzte Objekte zugreifenshared_ptrUndweak_ptrEine typische Anwendung.

5. Smart-Pointer-Löscher

6. Es wird empfohlen, make_shared anstelle von shared_ptr zu verwenden


Referenzartikel:Tiefes Verständnis der C++-Smartpointer