Condivisione della tecnologia

Sperimenta la potenza dei puntatori intelligenti

2024-07-12

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


Catalogo dei corsi



1. Conoscenza di base dei puntatori intelligenti

Svantaggi dei puntatori grezzi:

  1. dimenticaredeleteRilasciare risorse, causando la perdita di risorse
  2. esisteredeleteIl programma è terminato normalmente prima (ad esempio, ififmezzoreturn) o uscire in modo anomalo prima che sia troppo tardidelete, con conseguente perdita di risorse
  3. La stessa risorsa viene rilasciata più volte, causando il rilascio di puntatori selvaggi e l'arresto anomalo del programma.

In questo momento sono necessari puntatori intelligenti. La parola intelligenza dei puntatori intelligenti si riflette principalmente nel fatto che gli utenti non devono prestare attenzione al rilascio delle risorse, perché i puntatori intelligenti ti aiuteranno a gestire completamente il rilascio delle risorse. Lo garantiranno indipendentemente da come viene eseguita la logica del programma , verrà eseguito normalmente o genererà eccezioni.Quando le risorse scadono (ambito della funzione o fine del programma), verranno rilasciate.

Implementiamo noi stessi un semplice puntatore intelligente

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. I puntatori intelligenti sono incorporati nell'incapsulamento orientato agli oggetti di puntatori grezzi, inizializzando gli indirizzi delle risorse nel costruttore e rilasciando le risorse nel distruttore.
  2. Utilizzando la funzionalità di distruzione automatica dell'ambito degli oggetti nello stack, è garantito che le risorse vengano rilasciate nel distruttore del puntatore intelligente.

Pertanto, a causa delle caratteristiche di cui sopra, i puntatori intelligenti sono generalmente definiti nello stack. allo stesso tempo,Un puntatore intelligente è un oggetto di classe , un puntatore viene passato al costruttore di questa classe e il puntatore passato viene rilasciato nel distruttore.Poiché questo tipo di oggetto viene allocato e rilasciato nello stack, verrà rilasciato automaticamente al termine della nostra funzione (o programma).

Quindi, i puntatori intelligenti possono essere definiti nell'heap?Per esempioCSmartPtr* p = new CSmartPtr(new int);, la compilazione può passare, ma la definizione quipSebbene sia un tipo di puntatore intelligente, è essenzialmente un puntatore grezzo, quindipDevo ancora farlo manualmentedelete, torniamo al problema che abbiamo affrontato all'inizio con i semplici puntatori, quindi non usarlo in questo modo

Naturalmente, i puntatori intelligenti devono essere simili ai puntatori grezzi e devono anche fornire funzionalità comuni ai puntatori grezzi.*E->Le funzioni sovraccaricate dei due operatori sono veramente le stesse dei puntatori grezzi quando vengono utilizzate. Il codice è il seguente:

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. Puntatori intelligenti senza conteggio dei riferimenti

Il puntatore intelligente implementato nella sezione precedente è molto simile a un normale puntatore grezzo in uso, ma presenta ancora grossi problemi. Vedere il codice seguente:

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

Il codice in esecuzione si blocca direttamente perché il costruttore di copie predefinito esegue una copia superficiale.p1Ep2tiene lo stessonew intrisorsa,p2Quindi la distruzione libera prima le risorsep1Quando distrutto, diventadeletePuntatore selvaggio, il programma si blocca

Quindi, come risolvere i problemi causati dalla copia superficiale?riscrivereCSmartPtrDai un'occhiata al costruttore della copia

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

Ora, però, il codice funziona normalmentep1Ep2Vengono gestite due risorse diverse. Se gli utenti non le comprendono, penseranno erroneamente che ciò accadap1Ep2Gestisci la stessa risorsa

Pertanto, è sbagliato per noi scrivere il costruttore della copia in questo modo

Quindi, come risolvere il problema della copia superficiale dei puntatori intelligenti? Due metodi:Puntatori intelligenti senza conteggio dei riferimenti, puntatori intelligenti con conteggio dei riferimenti

Diamo prima un'occhiata a questa sezionePuntatori intelligenti senza conteggio dei riferimenti

  1. auto_ptr(C++98, ora deprecato)
  2. scoped_ptr(Aumenta la libreria)
  3. unique_ptr(C++11, consigliato)

Includere il file di intestazione quando si utilizza:#include <memory>

auto_ptr

Consideriamo prima questo codice

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

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

L’operazione è fallita. Perché?auto_ptrcodice sorgente

Inserisci qui la descrizione dell'immagine

Puoi vedere che la costruzione della copia chiamerà l'oggetto in entratareleasemetodo, questo metodo è mettere_RightLa risorsa puntata viene restituita alla nuovaauto_ptroggetto e allo stesso tempo_Right(vecchioauto_ptr)Di_Myptrimpostatonullptr

Insomma, è mettere il vecchioauto_ptrLa risorsa puntata viene data al nuovoauto_ptrTenuto, quello vecchio è impostato sunullptr .Pertanto, il codice precedente utilizza*ptr1Semplicemente sbagliato.auto_ptrLascia sempre che sia l'ultimo puntatore intelligente a gestire la risorsa)

COSÌ,auto_ptrPuò essere utilizzato in un contenitore? Vedere il codice seguente.

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

Pertanto, è deprecato in C++auto_ptr , a meno che lo scenario applicativo non sia molto semplice.Eauto_ptrDeprecato in C++11 e rimosso completamente in C++17

Riassumere:auto_ptrI puntatori intelligenti non hanno il conteggio dei riferimenti, quindi affrontano il problema della copia superficiale copiando direttamente il precedenteauto_ptrsono impostati sunullptr, lascia solo l'ultimoauto_ptrdetenere risorse

ambito_ptr

È necessario installare la libreria Boost, che include i file di intestazione.#include <boost/scoped_ptr.hpp>Pronto all'uso

guardascoped_ptrCodice sorgente:

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

si può vederescoped_ptrIl costruttore della copia e la funzione di assegnazione sono entrambi privatizzati, in modo che l'oggetto non supporti queste due operazioni e non possa essere chiamato, il che impedisce fondamentalmente il verificarsi di copie superficiali.

COSÌscoped_ptrNon può essere utilizzato nei contenitori. Se i contenitori si copiano o si assegnano valori tra loro, causeràscoped_ptrCostruttore di copia oggetto e funzione di assegnazione, errore di compilazione

scoped_ptrEauto_ptrLa differenza: Può essere spiegato dalla proprietà,auto_ptrLa proprietà delle risorse può essere trasferita a piacimento, mentrescoped_ptrLa proprietà non viene trasferita (perché i costruttori di copia e le funzioni di assegnazione sono disabilitate)

scoped_ptrGeneralmente non utilizzato

ptr_unico

Dai un'occhiata primaunique_ptrParte del codice sorgente:

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

Visto dall'alto,unique_ptrun po'scoped_ptrViene fatta la stessa cosa, ovvero le funzioni di costruzione della copia e di sovraccarico dell'assegnazione vengono disabilitate e agli utenti non è consentito l'utilizzounique_ptrEseguire la costruzione e l'assegnazione esplicite della copia per evitare il verificarsi di problemi di copia superficiale del puntatore intelligente.

Poiché non esiste un costruttore di copie,unique_ptr<int> p1(new int()); unique_ptr<int> p2(p1);Naturalmente è sbagliato

Maunique_ptrFornisce costruttori di copia e funzioni di assegnazione con parametri di riferimento rvalue, ovverounique_ptrI puntatori intelligenti possono eseguire operazioni di costruzione e assegnazione di copie tramite riferimenti rvalue o generareunique_ptrUn posto per oggetti temporanei, ad esunique_ptrCome valore restituito dalla funzione, il codice di esempio è il seguente:

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

Quindi utilizzareunique_ptrIl vantaggio è che gli utenti possono utilizzare qualcosa di simileunique_ptr<int> p2(move(p1));dichiarazione, lo si vede chiaramentep1Risorse trasferite ap2p1Non vengono trattenute più risorseauto_ptrNon lo sarà esplicitamentemoveScritta, l’intenzione non è ovvia. Se non capisci lo strato sottostante, lo utilizzerai in modo errato.

Allo stesso tempo, daunique_ptrCome si può vedere dal nome, alla fine può esserci solo un puntatore intelligente che fa riferimento a una risorsa. Pertanto, si consiglia di dare priorità all'utilizzo dei puntatori intelligenti senza conteggio dei riferimenti.unique_ptr

3. Puntatori intelligenti con conteggio dei riferimenti

I puntatori intelligenti con conteggio dei riferimenti includono principalmenteshared_ptrEweak_ptr, con conteggio dei riferimentibeneficioCioè, più puntatori intelligenti possono gestire la stessa risorsa. Quindi cosa sono i puntatori intelligenti con conteggio dei riferimenti?

Con conteggio dei riferimenti: corrisponde a un conteggio dei riferimenti per la risorsa di ciascun oggetto.

Quando più puntatori intelligenti possono puntare alla stessa risorsa, ogni puntatore intelligente aggiungerà 1 al conteggio dei riferimenti della risorsa. Quando un puntatore intelligente viene distrutto, diminuirà anche il conteggio dei riferimenti della risorsa di 1, in modo che ultimo puntatore intelligente verrà Quando il conteggio dei riferimenti di una risorsa diminuisce da 1 a 0, significa che la risorsa può essere rilasciata. Questo è il concetto di conteggio dei riferimenti.

  • Durante il conteggio dei riferimentimeno 1 non è 0Quando , il puntatore intelligente corrente non utilizza più questa risorsa, ma ci sono altri puntatori intelligenti che utilizzano questa risorsa. Il puntatore intelligente corrente non può distruggere questa risorsa e può solo andare direttamente
  • Durante il conteggio dei riferimentiDiminuire da 1 a 0Quando, significa che il puntatore intelligente corrente è l'ultimo puntatore intelligente a utilizzare questa risorsa, quindi è responsabile del rilascio di questa risorsa.

Simulare l'implementazione di un puntatore intelligente con il conteggio dei riferimenti

Prendi ciò che è stato implementato in precedenzaCSmartPtrApporta modifiche e carica direttamente il codice:

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

Ciò consente a più puntatori intelligenti di gestire la stessa risorsa.

Tuttavia, i puntatori intelligenti che implementiamo ora non sono thread-safe e non possono essere utilizzati in scenari multi-thread.Implementato da Curryshared_ptrEweak_ptrÈ sicuro per i thread!

Problema di riferimento incrociato shared_ptr

  • shared_ptrpotentePuntatori intelligenti (possono modificare il conteggio dei riferimenti delle risorse)
  • weak_ptrDebolePuntatori intelligenti (non modificare il conteggio dei riferimenti della risorsa)

Cosa abbiamo implementato nella sezione precedenteCSmartPtrAnche un potente puntatore intelligente (può modificare il conteggio dei riferimenti della risorsa)

Lo si può intendere in questo modo: Puntatore intelligente debole osservare Puntatori intelligenti forti Puntatori intelligenti fortiosservare Risorse (memoria)

COSÌ,Forte problema di riferimento incrociato del puntatore intelligente (riferimento circolare). quindi cos'è?Vieni a dare un'occhiata

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

risultato dell'operazione:

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

Come puoi vedere, A e B non vengono distrutti, il che significa che i riferimenti incrociati causerannonewLe risorse che escono non possono essere rilasciate, con conseguente perdita di risorse!

analizzare:
Inserisci qui la descrizione dell'immagineInserisci qui la descrizione dell'immagine
fuorimainambito della funzione,paEpbDue oggetti locali vengono distrutti e i conteggi dei riferimenti dell'oggetto A e dell'oggetto B vengono ridotti rispettivamente da 2 a 1. Le condizioni per il rilascio di A e B non possono essere soddisfatte (la condizione per il rilascio è che i conteggi dei riferimenti di A e B siano ridotti a 0), provocandone così duenewGli oggetti A e B usciti non possono essere rilasciati, causando perdite di memoria. Questo è il problemaForte problema di riferimento incrociato del puntatore intelligente (riferimento circolare).

Soluzione: utilizzare puntatori intelligenti potenti quando si definiscono gli oggetti e utilizzare puntatori intelligenti deboli quando si fa riferimento agli oggetti.

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

risultato dell'operazione:

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

può essere visto, fuorimainambito della funzione,paEpbI due oggetti locali vengono distrutti e i conteggi dei riferimenti rispettivamente dell'oggetto A e dell'oggetto B vengono ridotti da 1 a 0, raggiungendo le condizioni per rilasciare A e B. Pertanto,newL'oggetto A e l'oggetto B che sono usciti sono stati distrutti, il che ha risolto il problema.Forte problema di riferimento incrociato del puntatore intelligente (riferimento circolare).

Inserisci qui la descrizione dell'immagine
si può vedere,weak_ptrI puntatori intelligenti deboli non modificheranno il conteggio dei riferimenti della risorsa, il che significa che il puntatore intelligente debole osserva solo se l'oggetto è vivo (se il conteggio dei riferimenti è 0) e non può utilizzare la risorsa.

Quindi, se in questo momento, aggiungi la classe Avoid testA() { cout << "非常好用的方法!!" << endl; }Tale metodo si aggiunge a Bvoid func() { _ptra->testA(); }Va bene chiamare questo metodo?

  • Non posso, perché il puntatore intelligente debole è solo un osservatore e non può utilizzare risorse, cioè non fornisce*E->Le funzioni sovraccaricate degli operatori non possono utilizzare funzioni simili ai puntatori grezzi

Soluzione:

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

Sebbene weak_ptrnon possiede l'oggetto, ma può passarlolock()Il metodo tenta di ottenere un puntatore all'oggettoshared_ptr, se l'oggetto è ancora vivo (cioè ce ne sono altrishared_ptrindicarlo),lock()restituirà un puntatore all'oggettoshared_ptr; Altrimenti verrà restituito un valore vuotoshared_ptr

utilizzolock()Uno scenario tipico per i metodi è quando è richiesto l'accesso temporaneoweak_ptrl'oggetto puntato e allo stesso tempo non si desidera aumentare il conteggio della durata dell'oggetto

risultato dell'operazione:

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

A questo punto puoi vedere che è stato chiamato correttamente!

4. Problemi di sicurezza dei thread quando i multithread accedono a oggetti condivisi

Vediamone unoProblemi di sicurezza dei thread quando si accede a oggetti condivisi da più thread: Il thread A e il thread B accedono a un oggetto condiviso. Se il thread A sta distruggendo l'oggetto, il thread B deve chiamare il metodo membro dell'oggetto condiviso. In questo momento, il thread A potrebbe aver finito di distruggere l'oggetto e il thread B no Se provi ad accedere all'oggetto, si verificherà un errore imprevisto.

Per prima cosa guarda il seguente codice:

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 esecuzioneq->testA();Quando viene fatta questa affermazione, ilmainL'oggetto condiviso è stato distrutto dal thread, il che è ovviamente irragionevole.

Se vuoi passareqSe il puntatore vuole accedere all'oggetto A, deve determinare se l'oggetto A è vivo. Se l'oggetto A è vivo, chiamatestANon ci sono problemi con il metodo; se l'oggetto A è stato distrutto, chiamatestA ha un problema!Vale a direqQuando si accede all'oggetto A, è necessario rilevare se l'oggetto A è vivo. Come risolvere questo problema? È necessario utilizzare puntatori intelligenti forti e deboli.

Diamo un'occhiata a questo codice:

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

risultato dell'operazione:

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

Puoi vedere che sta funzionandosp->testA();,PerchémainIl thread ha chiamatot1.join()Il metodo attende la fine del thread figlio in questo momentowppassaggiolockPromosso con successo asp

Modifica il codice sopra:

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

risultato dell'operazione:

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

Come puoi vedere, impostiamo un ambito e anche sett1Per separare i thread, utilizza i puntatori intelligentipQuando A viene distrutto fuori dall'ambito, verrà stampato in questo momentoA对象已经析构,不能再访问!, questo èwppassaggiolockImpossibile eseguire correttamente l'aggiornamento asp

Quanto sopra è il problema di sicurezza del thread quando i multithread accedono a oggetti condivisi. Questo è un riferimentoshared_ptrEweak_ptrUna tipica applicazione.

5. Eliminatore di puntatori intelligente

6. Si consiglia di utilizzare make_shared invece di shared_ptr


Articolo di riferimento:Conoscenza approfondita dei puntatori intelligenti C++