Κοινή χρήση τεχνολογίας

Ζήστε τη δύναμη των έξυπνων δεικτών

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Γενικά δεν χρησιμοποιείται

μοναδικό_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

Αυτό επιτρέπει σε πολλούς έξυπνους δείκτες να διαχειρίζονται τον ίδιο πόρο.

Ωστόσο, οι έξυπνοι δείκτες που εφαρμόζουμε τώρα δεν είναι ασφαλείς για νήμα και δεν μπορούν να χρησιμοποιηθούν σε σενάρια πολλαπλών νημάτων.Υλοποιήθηκε από τον Curryshared_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

Όπως μπορείτε να δείτε, το Α και το Β δεν καταστρέφονται, πράγμα που σημαίνει ότι η διασταύρωση θα προκαλέσειnewΟι πόροι που βγαίνουν δεν μπορούν να απελευθερωθούν, με αποτέλεσμα τη διαρροή πόρων!

αναλύει:
Εισαγάγετε την περιγραφή της εικόνας εδώΕισαγάγετε την περιγραφή της εικόνας εδώ
έξωmainεύρος λειτουργίας,paκαιpbΔύο τοπικά αντικείμενα καταστρέφονται και οι αριθμοί αναφοράς του αντικειμένου Α και του αντικειμένου Β μειώνονται από 2 σε 1 αντίστοιχα. στο 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Τα δύο τοπικά αντικείμενα καταστρέφονται και οι αριθμοί αναφοράς του αντικειμένου Α και του αντικειμένου Β αντίστοιχα μειώνονται από 1 σε 0, φτάνοντας τις συνθήκες για την απελευθέρωση των Α και Β. Επομένως,newΤο αντικείμενο Α και το αντικείμενο Β που βγήκαν καταστράφηκαν, γεγονός που έλυσε το πρόβλημα.Ισχυρό πρόβλημα διασταύρωσης έξυπνου δείκτη (κυκλική αναφορά).

Εισαγάγετε την περιγραφή της εικόνας εδώ
μπορεί να ειδωθεί,weak_ptrΟι αδύναμοι έξυπνοι δείκτες δεν θα αλλάξουν το πλήθος αναφοράς του πόρου, πράγμα που σημαίνει ότι ο αδύναμος έξυπνος δείκτης παρατηρεί μόνο εάν το αντικείμενο είναι ζωντανό (αν το πλήθος αναφοράς είναι 0) και δεν μπορεί να χρησιμοποιήσει τον πόρο.

Στη συνέχεια, αν αυτή τη στιγμή, προσθέστε στην τάξη Αvoid testA() { cout << "非常好用的方法!!" << endl; }Μια τέτοια μέθοδος προσθέτει στο Βvoid 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. Ζητήματα ασφάλειας νημάτων όταν τα πολλαπλά νήματα έχουν πρόσβαση σε κοινόχρηστα αντικείμενα

Ας δούμε έναΖητήματα ασφάλειας νημάτων κατά την πρόσβαση σε κοινόχρηστα αντικείμενα από πολλαπλά νήματα: Το νήμα Α και το νήμα Β έχουν πρόσβαση σε ένα κοινόχρηστο αντικείμενο, το νήμα Β πρέπει να καλέσει τη μέθοδο μέλους του κοινόχρηστου αντικειμένου Εάν προσπαθήσετε να αποκτήσετε πρόσβαση στο αντικείμενο, θα προκύψει ένα απροσδόκητο σφάλμα.

Πρώτα κοιτάξτε τον παρακάτω κώδικα:

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 είναι ζωντανό, καλέστε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++