Berbagi teknologi

Rasakan kekuatan petunjuk cerdas

2024-07-12

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


Katalog Kursus



1. Pengetahuan dasar tentang smart pointer

Kerugian dari pointer mentah:

  1. lupadeleteMelepaskan sumber daya, menyebabkan kebocoran sumber daya
  2. adadeleteProgram keluar secara normal sebelumnya (misalnya, jikaiftengahreturn) atau keluar secara tidak normal sebelum terlambatdelete, menyebabkan kebocoran sumber daya
  3. Sumber daya yang sama dilepaskan beberapa kali, menyebabkan pointer liar dilepaskan dan program terhenti.

Pada saat ini, petunjuk cerdas sangat dibutuhkan. Kata intelijen dari smart pointer terutama tercermin dalam kenyataan bahwa pengguna tidak perlu memperhatikan pelepasan sumber daya, karena smart pointer akan membantu Anda sepenuhnya mengelola pelepasan sumber daya. Ini akan memastikan bahwa tidak peduli bagaimana logika program berjalan , itu akan dijalankan secara normal atau menghasilkan pengecualian.Ketika sumber daya habis masa berlakunya (lingkup fungsi atau program berakhir), sumber daya tersebut akan dilepaskan.

Mari kita terapkan sendiri penunjuk cerdas sederhana

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. Smart pointer diwujudkan dalam enkapsulasi pointer mentah berorientasi objek, menginisialisasi alamat sumber daya di konstruktor, dan melepaskan sumber daya di destruktor.
  2. Memanfaatkan fitur penghancuran lingkup otomatis objek di tumpukan, sumber daya dijamin akan dilepaskan di destruktor penunjuk cerdas.

Oleh karena itu, karena karakteristik di atas, smart pointer umumnya ditentukan di stack. pada saat yang sama,Penunjuk cerdas adalah objek kelas , sebuah pointer diteruskan di konstruktor kelas ini, dan pointer yang diteruskan dilepaskan di destruktor.Karena jenis objek ini dialokasikan dan dilepaskan di tumpukan, maka objek tersebut akan dilepaskan secara otomatis ketika fungsi (atau program) kita berakhir.

Jadi, bisakah smart pointer didefinisikan di heap?MisalnyaCSmartPtr* p = new CSmartPtr(new int);, kompilasi bisa lolos, tetapi definisinya di sinipMeskipun ini adalah tipe penunjuk cerdas, pada dasarnya ini adalah penunjuk mentahpMasih perlu melakukannya secara manualdelete, itu kembali ke masalah yang kita hadapi dengan petunjuk kosong di awal, jadi jangan gunakan seperti ini

Tentu saja, smart pointer harus serupa dengan raw pointer, dan juga harus menyediakan fitur umum dari raw pointer.*Dan->Fungsi kelebihan kedua operator ini benar-benar sama dengan raw pointer saat digunakan.

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. Penunjuk cerdas tanpa penghitungan referensi

Smart pointer yang diimplementasikan pada bagian sebelumnya sangat mirip dengan raw pointer biasa yang digunakan, namun masih terdapat masalah besar Lihat kode berikut:

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

Kode yang berjalan langsung mogok karena konstruktor penyalinan default melakukan penyalinan dangkal.p1Danp2memegang hal yang samanew intsumber,p2Penghancuran pertama-tama melepaskan sumber dayanya, lalup1Ketika dirusak, ia menjadideletePenunjuk liar, program macet

Jadi, bagaimana cara mengatasi masalah yang disebabkan oleh penyalinan dangkal?menulis kembaliCSmartPtrLihatlah konstruktor salinan

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

Namun, sekarang kodenya berjalan normal pada saat inip1Danp2Dua sumber daya berbeda dikelola. Jika pengguna tidak memahaminya, mereka akan salah mengira demikianp1Danp2Kelola sumber daya yang sama

Oleh karena itu, salah jika kita menulis copy konstruktor seperti ini

Jadi, bagaimana cara mengatasi masalah salinan dangkal dari smart pointer? Dua metode:Penunjuk cerdas tanpa penghitungan referensi, penunjuk cerdas dengan penghitungan referensi

Mari kita lihat bagian ini terlebih dahuluPointer cerdas tanpa penghitungan referensi

  1. auto_ptr(C++98, sekarang tidak digunakan lagi)
  2. scoped_ptr(Meningkatkan perpustakaan)
  3. unique_ptr(C++11, disarankan)

Sertakan file header saat menggunakan:#include <memory>

auto_ptr

Mari pertimbangkan kode ini terlebih dahulu

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

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

Operasinya gagal. Mengapa?auto_ptrKode sumber

Masukkan deskripsi gambar di sini

Anda dapat melihat bahwa konstruksi salinan akan memanggil objek yang masukreleasemetode, metode ini adalah dengan menempatkan_RightSumber daya yang ditunjuk dikembalikan ke yang baruauto_ptrobjek, dan pada saat yang sama_Right(tuaauto_ptr)dari_Myptrmulainullptr

Singkatnya, ini adalah menempatkan yang lamaauto_ptrSumber daya yang ditunjuk diberikan kepada yang baruauto_ptrDitahan, yang lama disetel kenullptr .Oleh karena itu, kode di atas digunakan*ptr1Salah saja.auto_ptrSelalu biarkan penunjuk cerdas terakhir mengelola sumber daya)

Jadi,auto_ptrApakah bisa digunakan dalam container? Lihat kode berikut.

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

Oleh karena itu, ini tidak digunakan lagi di C++auto_ptr , kecuali skenario penerapannya sangat sederhana.Danauto_ptrTidak digunakan lagi di C++11 dan dihapus seluruhnya di C++17

Meringkaskan:auto_ptrSmart pointer tidak memiliki penghitungan referensi, sehingga menangani masalah penyalinan dangkal dengan menyalin langsung yang sebelumnyaauto_ptrdiatur kenullptr, biarlah yang terakhir sajaauto_ptrmemegang sumber daya

scoped_ptr

Anda perlu menginstal perpustakaan Boost, yang menyertakan file header.#include <boost/scoped_ptr.hpp>Siap digunakan

Lihatlahscoped_ptrKode sumber:

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

dapat dilihatscoped_ptrKonstruktor salinan dan fungsi penugasan keduanya diprivatisasi, sehingga objek tidak mendukung kedua operasi ini dan tidak dapat dipanggil, yang pada dasarnya mencegah terjadinya salinan dangkal.

Jadiscoped_ptrItu tidak dapat digunakan dalam wadah. Jika wadah menyalin atau memberikan nilai satu sama lain, hal itu akan menyebabkanscoped_ptrKonstruktor salinan objek dan fungsi penugasan, kesalahan kompilasi

scoped_ptrDanauto_ptrPerbedaan: Dapat dijelaskan berdasarkan kepemilikan,auto_ptrKepemilikan sumber daya dapat dialihkan sesuka hati, sedangkanscoped_ptrKepemilikan tidak ditransfer (karena konstruktor penyalinan dan fungsi penugasan dinonaktifkan)

scoped_ptrUmumnya tidak digunakan

unik_ptr

Coba lihat duluunique_ptrBagian dari kode sumber:

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

Dilihat dari atas,unique_ptrsedikitscoped_ptrHal yang sama dilakukan, yaitu fungsi konstruksi salinan dan penugasan yang kelebihan beban dinonaktifkan, dan pengguna dilarang untuk menggunakannyaunique_ptrLakukan konstruksi dan penetapan salinan eksplisit untuk mencegah terjadinya masalah salinan dangkal penunjuk pintar.

Karena tidak ada konstruktor penyalinan,unique_ptr<int> p1(new int()); unique_ptr<int> p2(p1);Tentu saja itu salah

Tetapiunique_ptrMenyediakan konstruktor penyalinan dan fungsi penugasan dengan parameter referensi nilai, yaitu,unique_ptrSmart pointer dapat melakukan konstruksi salinan dan operasi penugasan melalui referensi nilai, atau menghasilkanunique_ptrTempat untuk benda-benda sementara, misunique_ptrSebagai nilai kembalian dari fungsi tersebut, kode contohnya adalah sebagai berikut:

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

Kemudian gunakanunique_ptrManfaatnya adalah pengguna dapat menggunakan sesuatu sepertiunique_ptr<int> p2(move(p1));pernyataan tersebut, dapat dilihat dengan jelas bahwap1Sumber daya yang ditransfer kep2p1Tidak ada lagi sumber daya yang ditahanauto_ptrIni tidak akan secara eksplisitmoveSecara tertulis, maksudnya tidak jelas. Jika Anda tidak memahami lapisan yang mendasarinya, Anda akan salah menggunakannya.

Pada saat yang sama, dariunique_ptrSeperti namanya, hanya ada satu smart pointer yang pada akhirnya merujuk pada suatu sumber daya. Oleh karena itu, disarankan untuk mengutamakan penggunaan smart pointer tanpa penghitungan referensi.unique_ptr

3. Penunjuk cerdas dengan penghitungan referensi

Petunjuk cerdas dengan penghitungan referensi terutama mencakupshared_ptrDanweak_ptr, dengan penghitungan referensikeuntunganArtinya, beberapa smart pointer dapat mengelola sumber daya yang sama. Jadi, apa yang dimaksud dengan smart pointer dengan penghitungan referensi?

Dengan penghitungan referensi: Cocokkan jumlah referensi untuk setiap sumber daya objek.

Ketika beberapa penunjuk cerdas diizinkan untuk menunjuk ke sumber daya yang sama, setiap penunjuk cerdas akan menambahkan 1 ke jumlah referensi sumber daya. Saat penunjuk cerdas dihancurkan, jumlah referensi sumber daya juga akan berkurang sebanyak 1, sehingga penunjuk pintar terakhir akan Ketika jumlah referensi suatu sumber daya berkurang dari 1 menjadi 0, ini berarti bahwa sumber daya tersebut dapat dilepaskan. Destruktor dari penunjuk pintar terakhir menangani pelepasan sumber daya.

  • Saat referensi menghitungdikurangi 1 bukan 0Ketika, penunjuk cerdas saat ini tidak lagi menggunakan sumber daya ini, tetapi ada penunjuk cerdas lain yang menggunakan sumber daya ini. Penunjuk cerdas saat ini tidak dapat merusak sumber daya ini dan hanya dapat langsung menuju
  • Saat referensi menghitungKurangi 1 menjadi 0Artinya, penunjuk cerdas saat ini adalah penunjuk cerdas terakhir yang menggunakan sumber daya ini, sehingga penunjuk cerdas bertanggung jawab atas pelepasan sumber daya ini.

Simulasikan penerapan penunjuk cerdas dengan penghitungan referensi

Ambil contoh apa yang telah diterapkan sebelumnyaCSmartPtrLakukan modifikasi dan unggah kode secara langsung:

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

Hal ini memungkinkan beberapa penunjuk cerdas untuk mengelola sumber daya yang sama.

Namun, petunjuk cerdas yang kami terapkan sekarang tidak aman untuk thread dan tidak dapat digunakan dalam skenario multi-thread.Diimplementasikan oleh Curryshared_ptrDanweak_ptrIni aman untuk benang!

masalah referensi silang shared_ptr

  • shared_ptrkuatSmart pointer (dapat mengubah jumlah referensi sumber daya)
  • weak_ptrlemahPetunjuk cerdas (jangan mengubah jumlah referensi sumber daya)

Apa yang kami terapkan di bagian sebelumnyaCSmartPtrJuga merupakan penunjuk cerdas yang kuat (dapat mengubah jumlah referensi sumber daya)

Hal ini dapat dipahami seperti ini: Penunjuk cerdas yang lemah mengamati Petunjuk cerdas yang kuat Petunjuk cerdas yang kuatmengamati Sumber daya (memori)

Jadi,Masalah referensi silang penunjuk pintar yang kuat (referensi melingkar). lalu apa itu?Datang dan lihatlah

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

hasil operasi:

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

Seperti yang Anda lihat, A dan B tidak dirusak, yang berarti referensi silang akan menyebabkannyanewSumber daya yang keluar tidak dapat dilepaskan sehingga mengakibatkan kebocoran sumber daya!

menganalisa:
Masukkan deskripsi gambar di siniMasukkan deskripsi gambar di sini
keluarmainruang lingkup fungsi,paDanpbDua objek lokal dihancurkan, dan jumlah referensi objek A dan objek B masing-masing berkurang dari 2 menjadi 1. Kondisi untuk melepaskan A dan B tidak dapat dipenuhi (kondisi untuk melepaskan adalah jumlah referensi A dan B berkurang ke 0), sehingga menyebabkan duanewObjek A dan B yang keluar tidak bisa dilepas sehingga menyebabkan kebocoran memoriMasalah referensi silang penunjuk pintar yang kuat (referensi melingkar).

Solusi: Gunakan smart pointer yang kuat saat mendefinisikan objek, dan gunakan smart pointer yang lemah saat mereferensikan objek.

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

hasil operasi:

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

bisa dilihat, keluarmainruang lingkup fungsi,paDanpbKedua objek lokal dihancurkan, dan jumlah referensi objek A dan objek B masing-masing dikurangi dari 1 menjadi 0, mencapai kondisi untuk melepaskan A dan B. Oleh karena itu,newObjek A dan objek B yang keluar dihancurkan, sehingga masalah terpecahkan.Masalah referensi silang penunjuk pintar yang kuat (referensi melingkar).

Masukkan deskripsi gambar di sini
dapat dilihat,weak_ptrSmart pointer yang lemah tidak akan mengubah jumlah referensi sumber daya, artinya smart pointer yang lemah hanya mengamati apakah objek tersebut hidup (apakah jumlah referensinya 0), dan tidak dapat menggunakan sumber daya.

Lalu kalau saat ini, tambahkan di kelas Avoid testA() { cout << "非常好用的方法!!" << endl; }Metode seperti itu menambah Bvoid func() { _ptra->testA(); }Bolehkah memanggil metode ini?

  • Tidak bisa, karena smart pointer yang lemah hanya sebagai pengamat dan tidak dapat menggunakan sumber daya, yaitu tidak menyediakan*Dan->Fungsi operator yang kelebihan beban tidak dapat menggunakan fungsi yang mirip dengan pointer mentah

Larutan:

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

Meskipun weak_ptrtidak memiliki benda tersebut, tetapi dapat lewatlock()metode mencoba untuk mendapatkan pointer ke objekshared_ptr, jika benda tersebut masih hidup (yaitu ada benda lainshared_ptrarahkan ke sana),lock()akan mengembalikan pointer ke objekshared_ptr; Jika tidak, nilai kosong akan dikembalikanshared_ptr

menggunakanlock()Skenario umum untuk metode adalah ketika akses sementara diperlukanweak_ptrobjek yang ditunjuknya, dan pada saat yang sama tidak ingin menambah jumlah umur objek tersebut

hasil operasi:

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

Pada titik ini Anda dapat melihat bahwa pemanggilannya benar!

4. Masalah keamanan thread ketika multi-thread mengakses objek bersama

Mari kita lihat satuMasalah keamanan thread saat mengakses objek bersama dari beberapa thread: Thread A dan thread B mengakses objek bersama. Jika thread A menghancurkan objek, thread B perlu memanggil metode anggota dari objek bersama. Pada saat ini, thread A mungkin telah selesai menghancurkan objek, dan thread B tidak akan melakukannya Jika Anda mencoba mengakses objek tersebut, kesalahan yang tidak terduga akan terjadi.

Pertama lihat kode berikut:

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

dalam eksekusiq->testA();Ketika pernyataan ini dibuat,mainObjek yang dibagikan telah dihancurkan oleh thread, yang jelas tidak masuk akal.

Jika Anda ingin lulusqJika pointer ingin mengakses objek A, ia perlu menentukan apakah objek A masih hidup. Jika objek A masih hidup, panggiltestATidak ada masalah dengan metode ini; jika objek A telah dirusak, panggiltestA punya masalah!ArtinyaqSaat mengakses objek A, Anda perlu mendeteksi apakah objek A masih hidup. Bagaimana mengatasi masalah ini?

Mari kita lihat kode ini:

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

hasil operasi:

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

Anda dapat melihatnya sedang berjalansp->testA();,KarenamainUtasnya memanggilt1.join()Metode ini menunggu thread anak berakhirwpluluslockBerhasil dipromosikan menjadisp

Ubah kode di atas:

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

hasil operasi:

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

Seperti yang Anda lihat, kami menetapkan cakupan dan juga menetapkant1Untuk memisahkan thread, biarkan smart pointerpKetika A dirusak di luar ruang lingkup, itu akan dicetak saat iniA对象已经析构,不能再访问!, itu adalahwpluluslockGagal berhasil meningkatkan versi kesp

Di atas adalah masalah keamanan thread ketika multi-thread mengakses objek bersamashared_ptrDanweak_ptrAplikasi yang khas.

5. Penghapus penunjuk pintar

6. Disarankan untuk menggunakan make_shared daripada shared_ptr


Artikel referensi:Pemahaman mendalam tentang pointer cerdas C++