τα στοιχεία επικοινωνίας μου
Ταχυδρομείο[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Μειονεκτήματα των ακατέργαστων δεικτών:
delete
Απελευθερώστε πόρους, προκαλώντας διαρροή πόρωνdelete
Το πρόγραμμα εξήλθε κανονικά πριν (για παράδειγμα, εάνif
Μέσηςreturn
) ή βγείτε ασυνήθιστα πριν να είναι πολύ αργάdelete
, που οδηγεί σε διαρροή πόρωνΑυτή τη στιγμή χρειάζονται έξυπνοι δείκτες. Η λέξη ευφυΐα των έξυπνων δεικτών αντικατοπτρίζεται κυρίως στο γεγονός ότι οι χρήστες δεν χρειάζεται να δώσουν προσοχή στην αποδέσμευση των πόρων, επειδή οι έξυπνοι δείκτες θα σας βοηθήσουν να διαχειριστείτε πλήρως την απελευθέρωση των πόρων , θα εκτελεστεί κανονικά ή θα δημιουργήσει εξαιρέσεις.Όταν λήξουν οι πόροι (εύρος λειτουργίας ή τέλος προγράμματος), θα απελευθερωθούν.
Ας εφαρμόσουμε μόνοι μας έναν απλό έξυπνο δείκτη
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T* ptr = nullptr) : mptr(ptr) {}
~CSmartPtr() { delete mptr; }
private:
T* mptr;
};
int main()
{
CSmartPtr<int> ptr(new int());
/*其它的代码...*/
/*
由于ptr是栈上的智能指针对象,不管是函数正常执行完,
还是运行过程中出现异常,栈上的对象都会自动调用析构函数,
在析构函数中进行了delete操作,保证释放资源
*/
return 0;
}
Επομένως, λόγω των παραπάνω χαρακτηριστικών, οι έξυπνοι δείκτες γενικά ορίζονται στη στοίβα. Την ίδια στιγμή,Ένας έξυπνος δείκτης είναι ένα αντικείμενο κλάσης , ένας δείκτης μεταβιβάζεται στον κατασκευαστή αυτής της κλάσης και ο δείκτης που πέρασε απελευθερώνεται στον καταστροφέα.Εφόσον αυτός ο τύπος αντικειμένου εκχωρείται και απελευθερώνεται στη στοίβα, θα απελευθερωθεί αυτόματα όταν τελειώσει η λειτουργία (ή το πρόγραμμά μας).
Λοιπόν, μπορούν να οριστούν έξυπνοι δείκτες στο σωρό;για παράδειγμαCSmartPtr* p = new CSmartPtr(new int);
, η συλλογή μπορεί να περάσει, αλλά ο ορισμός εδώp
Αν και είναι ένας έξυπνος τύπος δείκτη, είναι ουσιαστικά ένας ακατέργαστος δείκτης, έτσιp
Πρέπει ακόμα να το κάνετε χειροκίνηταdelete
, είναι πίσω στο πρόβλημα που αντιμετωπίσαμε με γυμνούς δείκτες στην αρχή, οπότε μην το χρησιμοποιείτε έτσι
Φυσικά, οι έξυπνοι δείκτες πρέπει να είναι παρόμοιοι με τους πρωτογενείς δείκτες και πρέπει επίσης να παρέχουν κοινά χαρακτηριστικά των μη επεξεργασμένων δεικτών.*
και->
Οι υπερφορτωμένες λειτουργίες των δύο τελεστών είναι πραγματικά οι ίδιες με τους πρωτογενείς δείκτες όταν χρησιμοποιούνται.
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T* ptr = nullptr) : mptr(ptr) {}
~CSmartPtr() { delete mptr; }
T& operator*() { return *mptr; }
T* operator->() { return mptr; }
private:
T* mptr;
};
int main()
{
CSmartPtr<int> ptr(new int());
*ptr = 20; // operator*()一定要返回引用,这样才可以赋值
cout << *ptr << endl; // 20
class Test
{
public:
void test() { cout << "call Test::test()" << endl; }
};
CSmartPtr<Test> ptr2 = new Test();
(*ptr2).test(); // (*ptr2)取出Test对象,用对象调用方法
// operator->()返回的是一个指针,实现了用指针调用函数
// 即(ptr2.operator->()) -> test()
ptr2->test();
return 0;
}
Ο έξυπνος δείκτης που εφαρμόστηκε στην προηγούμενη ενότητα είναι πολύ παρόμοιος με έναν συνηθισμένο δείκτη που χρησιμοποιείται, αλλά εξακολουθεί να έχει μεγάλα προβλήματα.
CSmartPtr<int> p1(new int());
CSmartPtr<int> p2(p1); // 拷贝构造
Ο κώδικας που εκτελείται διακόπτεται απευθείας επειδή ο προεπιλεγμένος κατασκευαστής αντιγραφής κάνει ένα ρηχό αντίγραφο.p1
καιp2
κρατάει το ίδιοnew int
πόρος,p2
Η καταστροφή πρώτα απελευθερώνει τους πόρους, στη συνέχειαp1
Όταν καταστρέφεται, γίνεταιdelete
Άγριο δείκτης, σφάλματα προγράμματος
Λοιπόν, πώς να λύσετε τα προβλήματα που προκαλούνται από τη ρηχή αντιγραφή;ξαναγράφωCSmartPtr
Ρίξτε μια ματιά στον κατασκευαστή αντιγραφής
CSmartPtr(const CSmartPtr<T>& src) { mptr = new T(*src.mptr); }
Τώρα ο κώδικας τρέχει κανονικά, ωστόσο, σε αυτό το σημείοp1
καιp2
Διαχειρίζονται δύο διαφορετικοί πόροι Εάν οι χρήστες δεν τους καταλαβαίνουν, θα το σκεφτούν λανθασμέναp1
καιp2
Διαχειριστείτε τον ίδιο πόρο
Επομένως, είναι λάθος να γράφουμε έτσι τον κατασκευαστή αντιγραφής
Λοιπόν, πώς να λύσετε το πρόβλημα ρηχής αντιγραφής των έξυπνων δεικτών; Δύο μέθοδοι:Έξυπνοι δείκτες χωρίς μέτρηση αναφορών, έξυπνοι δείκτες με μέτρηση αναφορών
Ας δούμε πρώτα αυτήν την ενότηταΈξυπνοι δείκτες χωρίς μέτρηση αναφορών
auto_ptr
(C++98, πλέον καταργήθηκε)scoped_ptr
(Ενίσχυση βιβλιοθήκης)unique_ptr
(C++11, συνιστάται)Συμπεριλάβετε το αρχείο κεφαλίδας όταν χρησιμοποιείτε:#include <memory>
Ας εξετάσουμε πρώτα αυτόν τον κώδικα
auto_ptr<int> ptr1(new int());
auto_ptr<int> ptr2(ptr1);
*ptr2 = 20;
cout << *ptr1 << endl;
Ας ρίξουμε μια ματιά.auto_ptr
πηγαίος κώδικας
Μπορείτε να δείτε ότι η κατασκευή αντιγράφου θα καλέσει το εισερχόμενο αντικείμενοrelease
μέθοδο, αυτή η μέθοδος είναι να θέσει_Right
Ο πόρος στον οποίο αναφέρεται επιστρέφεται στο νέοauto_ptr
αντικείμενο, και ταυτόχρονα_Right
(παλαιόςauto_ptr
)του_Myptr
οριστεί σεnullptr
Εν ολίγοις, είναι να βάλουμε το παλιόauto_ptr
Ο πόρος στον οποίο αναφέρεται δίνεται στο νέοauto_ptr
Κρατήθηκε, το παλιό έχει οριστεί ναnullptr
.Επομένως, ο παραπάνω κώδικας χρησιμοποιεί*ptr1
Απλά λάθος.(auto_ptr
Να αφήνετε πάντα τον τελευταίο έξυπνο δείκτη να διαχειρίζεται τον πόρο)
Ετσι,auto_ptr
Μπορεί να χρησιμοποιηθεί σε κοντέινερ Δείτε τον παρακάτω κώδικα.
int main()
{
vector<auto_ptr<int>> vec;
vec.push_back(auto_ptr<int>(new int(10)));
vec.push_back(auto_ptr<int>(new int(20)));
vec.push_back(auto_ptr<int>(new int(30)));
cout << *vec[0] << endl; // 10
vector<auto_ptr<int>> vec2 = vec;
/* 这里由于上面做了vector容器的拷贝,
相当于容器中的每一个元素都进行了拷贝构造,
原来vec中的智能指针全部为nullptr了,
再次访问就成访问空指针了,程序崩溃
*/
cout << *vec[0] << endl; // 程序崩溃
return 0;
}
Επομένως, έχει καταργηθεί στη C++auto_ptr
, εκτός αν το σενάριο εφαρμογής είναι πολύ απλό.καιauto_ptr
Καταργήθηκε στη C++11 και καταργήθηκε εξ ολοκλήρου στη C++17
Συνοψίζω:auto_ptr
Οι έξυπνοι δείκτες δεν έχουν μέτρηση αναφοράς, επομένως αντιμετωπίζουν το πρόβλημα της ρηχής αντιγραφής αντιγράφοντας απευθείας το προηγούμενοauto_ptr
έχουν οριστεί σεnullptr
, ας μόνο το τελευταίοauto_ptr
κρατήστε πόρους
Πρέπει να εγκαταστήσετε τη βιβλιοθήκη Boost, η οποία περιλαμβάνει τα αρχεία κεφαλίδας.#include <boost/scoped_ptr.hpp>
Ετοιμο για χρήση
Ρίξε μια ματιάscoped_ptr
Πηγαίος κώδικας:
template<class T> class scoped_ptr
{
private:
scoped_ptr(scoped_ptr const&);
scoped_ptr& operator=(scoped_ptr const&);
...
};
μπορεί να ειδωθείscoped_ptr
Ο κατασκευαστής αντιγραφής και η συνάρτηση εκχώρησης ιδιωτικοποιούνται, έτσι ώστε το αντικείμενο να μην υποστηρίζει αυτές τις δύο λειτουργίες και να μην μπορεί να κληθεί, κάτι που ουσιαστικά αποτρέπει την εμφάνιση ρηχών αντιγράφων.
Έτσιscoped_ptr
Δεν μπορεί να χρησιμοποιηθεί σε κοντέινερ Εάν τα κοντέινερ αντιγράψουν ή αντιστοιχίσουν τιμές το ένα στο άλλο, αυτό θα προκαλέσειscoped_ptr
Κατασκευαστής αντιγραφής αντικειμένου και συνάρτηση εκχώρησης, σφάλμα μεταγλώττισης
scoped_ptr
καιauto_ptr
Η διαφορά: Μπορεί να εξηγηθεί από την ιδιοκτησία,auto_ptr
Η κυριότητα των πόρων μπορεί να μεταβιβαστεί κατά βούληση, ενώscoped_ptr
Η ιδιοκτησία δεν μεταβιβάζεται (επειδή οι κατασκευαστές αντιγραφής και οι λειτουργίες ανάθεσης είναι απενεργοποιημένες)
scoped_ptr
Γενικά δεν χρησιμοποιείται
Ρίξτε μια ματιά πρώταunique_ptr
Μέρος του πηγαίου κώδικα:
template<class _Ty, class _Dx>
class unique_ptr
{
public:
/*提供了右值引用的拷贝构造函数*/
unique_ptr(unique_ptr&& _Right) { ... }
/*提供了右值引用的operator=赋值重载函数*/
unique_ptr& operator=(unique_ptr&& _Right) { ... }
/*
删除了unique_ptr的拷贝构造和赋值函数,
因此不能做unique_ptr智能指针对象的拷贝构造和赋值,
防止浅拷贝的发生
*/
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
};
Βλέποντας από ψηλά,unique_ptr
λίγοscoped_ptr
Γίνεται το ίδιο πράγμα, δηλαδή, οι υπερφορτωμένες λειτουργίες κατασκευής αντιγραφής και εκχώρησης είναι απενεργοποιημένες και οι χρήστες απαγορεύεται να χρησιμοποιούνunique_ptr
Εκτελέστε ρητή κατασκευή και αντιστοίχιση αντιγράφων για να αποτρέψετε την εμφάνιση προβλημάτων ρηχής αντιγραφής έξυπνου δείκτη.
Εφόσον δεν υπάρχει κατασκευαστής αντιγραφής,unique_ptr<int> p1(new int()); unique_ptr<int> p2(p1);
Φυσικά είναι λάθος
αλλάunique_ptr
Παρέχει κατασκευαστές αντιγραφής και συναρτήσεις εκχώρησης με παραμέτρους αναφοράς rvalue, δηλαδήunique_ptr
Οι έξυπνοι δείκτες μπορούν να εκτελέσουν εργασίες κατασκευής αντιγραφής και ανάθεσης μέσω αναφορών rvalue ή να δημιουργήσουνunique_ptr
Ένα μέρος για προσωρινά αντικείμενα, π.χunique_ptr
Ως επιστρεφόμενη τιμή της συνάρτησης, το δείγμα κώδικα έχει ως εξής:
// 示例1
unique_ptr<int> p1(new int());
unique_ptr<int> p2(move(p1)); // 使用了右值引用的拷贝构造
p2 = move(p1); // 使用了右值引用的operator=赋值重载函数
// 示例2
unique_ptr<int> test_uniqueptr()
{
unique_ptr<int> ptr(new int());
return ptr;
}
int main()
{
/*
此处调用test_uniqueptr函数,在return ptr代码处,
调用右值引用的拷贝构造和赋值函数
*/
unique_ptr<int> p = test_uniqueptr(); // 调用带右值引用的拷贝构造函数
p = test_uniqueptr(); // 调用带右值引用的operator=赋值重载函数
return 0;
}
Στη συνέχεια χρησιμοποιήστεunique_ptr
Το πλεονέκτημα είναι ότι οι χρήστες μπορούν να χρησιμοποιήσουν κάτι τέτοιοunique_ptr<int> p2(move(p1));
δήλωση, φαίνεται ξεκάθαρα ότιp1
Μεταφέρθηκαν πόροι σεp2
,p1
Δεν υπάρχουν άλλοι πόροι εάν χρησιμοποιείτεauto_ptr
Δεν θα είναι ρητάmove
Διαγραμμένο, η πρόθεση δεν είναι προφανής Εάν δεν καταλαβαίνετε το υποκείμενο επίπεδο, θα το χρησιμοποιήσετε εσφαλμένα.
Ταυτόχρονα, απόunique_ptr
Όπως φαίνεται από το όνομα, μπορεί να υπάρχει μόνο ένας έξυπνος δείκτης που αναφέρεται σε έναν πόρο στο τέλος, επομένως, συνιστάται να δίνεται προτεραιότητα στη χρήση έξυπνων δεικτών χωρίς μέτρηση αναφοράς.unique_ptr
Οι έξυπνοι δείκτες με μέτρηση αναφοράς περιλαμβάνουν κυρίωςshared_ptr
καιweak_ptr
, με μέτρηση αναφοράςόφελοςΔηλαδή, πολλοί έξυπνοι δείκτες μπορούν να διαχειριστούν τον ίδιο πόρο, λοιπόν, τι είναι οι έξυπνοι δείκτες με μέτρηση αναφοράς;
Με μέτρηση αναφοράς: Αντιστοιχίστε έναν αριθμό αναφοράς για τον πόρο κάθε αντικειμένου.
Όταν επιτρέπεται σε πολλούς έξυπνους δείκτες να δείχνουν προς τον ίδιο πόρο, κάθε έξυπνος δείκτης θα προσθέσει 1 στο πλήθος αναφοράς του πόρου, όταν ένας έξυπνος δείκτης καταστρέφεται, θα μειώσει επίσης τον αριθμό αναφοράς του πόρου κατά 1. Ο τελευταίος έξυπνος δείκτης θα μειωθεί από το 1 στο 0, αυτό σημαίνει ότι ο καταστροφέας του τελευταίου έξυπνου δείκτη χειρίζεται την απελευθέρωση του πόρου.
Πάρτε αυτό που εφαρμόστηκε προηγουμένωςCSmartPtr
Κάντε τροποποιήσεις και μεταφορτώστε τον κώδικα απευθείας:
// 对资源进行引用计数的类
template<typename T>
class RefCnt
{
public:
RefCnt(T* ptr = nullptr) : mptr(ptr)
{
if (mptr != nullptr)
mcount = 1;
}
void addRef() { mcount++; } // 增加资源的引用计数
int delRef() { return --mcount; }
private:
T* mptr;
int mcount;
};
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T* ptr = nullptr) : mptr(ptr)
{
// 智能指针构造的时候给资源建立引用计数对象
mpRefCnt = new RefCnt<T>(mptr);
}
~CSmartPtr()
{
if (0 == mpRefCnt->delRef())
{
delete mptr;
mptr = nullptr;
}
}
// 实现拷贝构造
CSmartPtr(const CSmartPtr<T>& src)
:mptr(src.mptr), mpRefCnt(src.mpRefCnt)
{
if (mptr != nullptr)
mpRefCnt->addRef();
}
CSmartPtr<T>& operator=(const CSmartPtr<T>& src)
{
// 防止自赋值
if (this == &src)
return *this;
// 本身指向的资源减1
// 如果减1为0释放资源;如果减1不为0直接走
if (0 == mpRefCnt->delRef()) { delete mptr; }
mptr = src.mptr;
mpRefCnt = src.mpRefCnt;
mpRefCnt->addRef();
return *this;
}
T& operator*() { return *mptr; }
T* operator->() { return mptr; }
private:
T* mptr; // 指向资源的指针
RefCnt<T>* mpRefCnt; // 指向该资源引用计数对象的指针
};
// 那么现在就不会报错了,不会对同一个资源释放多次
CSmartPtr<int> ptr1(new int());
CSmartPtr<int> ptr2(ptr1);
CSmartPtr<int> ptr3;
ptr3 = ptr2;
*ptr1 = 20;
cout << *ptr2 << " " << *ptr3 << endl; // 20 20
Αυτό επιτρέπει σε πολλούς έξυπνους δείκτες να διαχειρίζονται τον ίδιο πόρο.
Ωστόσο, οι έξυπνοι δείκτες που εφαρμόζουμε τώρα δεν είναι ασφαλείς για νήμα και δεν μπορούν να χρησιμοποιηθούν σε σενάρια πολλαπλών νημάτων.Υλοποιήθηκε από τον Curryshared_ptr
καιweak_ptr
Είναι ασφαλές με νήμα!
shared_ptr
:ισχυρόςΈξυπνοι δείκτες (μπορούν να αλλάξουν τον αριθμό αναφοράς των πόρων)weak_ptr
:αδύναμοςΈξυπνοι δείκτες (μην αλλάξετε το πλήθος αναφορών του πόρου)Τι εφαρμόσαμε στην προηγούμενη ενότηταCSmartPtr
Επίσης ένας ισχυρός έξυπνος δείκτης (μπορεί να αλλάξει τον αριθμό αναφοράς του πόρου)
Μπορεί να γίνει κατανοητό έτσι: Αδύναμος έξυπνος δείκτης παρατηρώ Ισχυροί έξυπνοι δείκτες Ισχυροί έξυπνοι δείκτεςπαρατηρώ Πόροι (μνήμη)
Ετσι,Ισχυρό πρόβλημα διασταύρωσης έξυπνου δείκτη (κυκλική αναφορά). τι είναι τότε;Ελάτε να ρίξετε μια ματιά
class B; // 前置声明类B
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
shared_ptr<B> _ptrb; // 指向B对象的智能指针
};
class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
shared_ptr<A> _ptra; // 指向A对象的智能指针
};
int main()
{
shared_ptr<A> pa(new A()); // pa指向A对象,A的引用计数为1
shared_ptr<B> pb(new B()); // pb指向B对象,B的引用计数为1
pa->_ptrb = pb; // A对象的成员变量_ptrb也指向B对象,B的引用计数为2
pb->_ptra = pa; // B对象的成员变量_ptra也指向A对象,A的引用计数为2
cout << pa.use_count() << endl;
cout << pb.use_count() << endl;
return 0;
}
αποτέλεσμα λειτουργίας:
A()
B()
2
2
Όπως μπορείτε να δείτε, το Α και το Β δεν καταστρέφονται, πράγμα που σημαίνει ότι η διασταύρωση θα προκαλέσει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;
}
αποτέλεσμα λειτουργίας:
A()
B()
1
1
~B()
~A()
φαίνεται, έξω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;
}
Αν και
weak_ptr
δεν κατέχει το αντικείμενο, αλλά μπορεί να περάσειlock()
Η μέθοδος επιχειρεί να αποκτήσει έναν δείκτη στο αντικείμενοshared_ptr
, εάν το αντικείμενο είναι ακόμα ζωντανό (δηλαδή υπάρχουν άλλαshared_ptr
δείξτε το),lock()
θα επιστρέψει έναν δείκτη στο αντικείμενοshared_ptr
Διαφορετικά, θα επιστραφεί μια κενή τιμήshared_ptr
χρήση
lock()
Ένα τυπικό σενάριο για τις μεθόδους είναι όταν απαιτείται προσωρινή πρόσβασηweak_ptr
το αντικείμενο που δείχνει, και ταυτόχρονα δεν θέλετε να αυξήσετε τη διάρκεια ζωής του αντικειμένου
αποτέλεσμα λειτουργίας:
A()
B()
1
1
非常好用的方法!!
~B()
~A()
Σε αυτό το σημείο μπορείτε να δείτε ότι ονομάστηκε σωστά!
Ας δούμε έναΖητήματα ασφάλειας νημάτων κατά την πρόσβαση σε κοινόχρηστα αντικείμενα από πολλαπλά νήματα: Το νήμα Α και το νήμα Β έχουν πρόσβαση σε ένα κοινόχρηστο αντικείμενο, το νήμα Β πρέπει να καλέσει τη μέθοδο μέλους του κοινόχρηστου αντικειμένου Εάν προσπαθήσετε να αποκτήσετε πρόσβαση στο αντικείμενο, θα προκύψει ένα απροσδόκητο σφάλμα.
Πρώτα κοιτάξτε τον παρακάτω κώδικα:
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void testA() { cout << "非常好用的方法!!" << endl; }
};
// 子线程
void handler01(A* q)
{
// 睡眠两秒,此时main主线程已经把A对象给delete析构掉了
std::this_thread::sleep_for(std::chrono::seconds(2));
q->testA();
}
// main线程
int main()
{
A* p = new A();
thread t1(handler01, p);
delete p;
//阻塞当前线程,直到调用join()的线程结束
t1.join();
return 0;
}
σε εκτέλεσηq->testA();
Όταν γίνεται αυτή η δήλωση, τοmain
Το κοινόχρηστο αντικείμενο έχει καταστραφεί από το νήμα, κάτι που είναι προφανώς παράλογο.
Αν θέλεις να περάσειςq
Εάν ο δείκτης θέλει να έχει πρόσβαση στο αντικείμενο A, πρέπει να προσδιορίσει εάν το αντικείμενο A είναι ζωντανό, καλέστεtestA
Δεν υπάρχει πρόβλημα με τη μέθοδο εάν το αντικείμενο A έχει καταστραφεί, καλέστεtestA
έχει πρόβλημα!Δηλαδήq
Κατά την πρόσβαση στο αντικείμενο A, πρέπει να εντοπίσετε εάν το αντικείμενο A είναι ζωντανό.
Ας δούμε αυτόν τον κώδικα:
// 子线程
void handler01(weak_ptr<A> wp)
{
// 睡眠两秒
std::this_thread::sleep_for(std::chrono::seconds(2));
shared_ptr<A> sp = wp.lock();
if (sp != nullptr)
sp->testA();
else
cout << "A对象已经析构,不能再访问!" << endl;
}
// main线程
int main()
{
shared_ptr<A> p(new A());
thread t1(handler01, weak_ptr<A>(p));
t1.join();
return 0;
}
αποτέλεσμα λειτουργίας:
A()
非常好用的方法!!
~A()
Μπορείτε να δείτε ότι τρέχειsp->testA();
,επειδήmain
Κάλεσε το νήμαt1.join()
Η μέθοδος περιμένει να τελειώσει το θυγατρικό νήμα αυτή τη στιγμήwp
πέρασμαlock
Προήχθη με επιτυχία σεsp
Τροποποιήστε τον παραπάνω κωδικό:
// 子线程
void handler01(weak_ptr<A> wp)
{
// 睡眠两秒
std::this_thread::sleep_for(std::chrono::seconds(2));
shared_ptr<A> sp = wp.lock();
if (sp != nullptr)
sp->testA();
else
cout << "A对象已经析构,不能再访问!" << endl;
}
// main线程
int main()
{
{
shared_ptr<A> p(new A());
thread t1(handler01, weak_ptr<A>(p));
t1.detach();
}
std::this_thread::sleep_for(std::chrono::seconds(3));
return 0;
}
αποτέλεσμα λειτουργίας:
A()
~A()
A对象已经析构,不能再访问!
Όπως μπορείτε να δείτε, ορίσαμε ένα πεδίο εφαρμογής και επίσης ορίσαμεt1
Για να διαχωρίσετε τα νήματα, αφήστε έξυπνους δείκτεςp
Όταν το A καταστρέφεται εκτός πεδίου εφαρμογής, θα εκτυπωθεί αυτήν τη στιγμήA对象已经析构,不能再访问!
, αυτό είναιwp
πέρασμαlock
Απέτυχε η επιτυχής αναβάθμιση σεsp
Το παραπάνω είναι το ζήτημα της ασφάλειας νημάτων όταν τα πολλαπλά νήματα έχουν πρόσβαση σε κοινόχρηστα αντικείμεναshared_ptr
καιweak_ptr
Μια τυπική εφαρμογή.
Άρθρο αναφοράς:Βαθιά κατανόηση των έξυπνων δεικτών C++