〖C++11〗スマートポインタの詳細説明

「はじめに」の記事は、C++11 のスマート ポインターの側面について説明しています。 

「所属コラム」Cクワック

《作者》メイプルリーフ氏(fy)

『座右の銘』自分を磨きながら進む

「メイプルリーフさんは少し知的障害があります」

「1記事につき1文」 

人生は墓行きの電車だ

途中、停留所がたくさんありますが、

誰かに最初から最後まで一緒に歩いてもらうのは難しいです。

同伴者が車から降りたいときは、

諦めなくても感謝の気持ちは持つべきです。

それから手を振ってお別れです。

—— 宮崎駿

目次

1. スマート ポインターが必要なのはなぜですか?

1.1 メモリリークの問題

1.2 スマート ポインタを使用して解決する

1.3 スマートポインタの原理

2、C++ スマート ポインター

2.1 std::auto_ptr

2.2 std::unique_ptr

 2.3 std::shared_ptr

2.4shared_ptr スレッドの安全性の問題

2.5 std::weak_ptr

3. C++11のスマートポインタとブーストの関係


1. スマート ポインターが必要なのはなぜですか?

1.1 メモリリークの問題

メモリ リークについては、たとえば次のコードがあります。

#include <iostream>
using namespace std;

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	int* p1 = new int;
    int* p2 = new int;
	cout << div() << endl;
	delete p1;
    delete p2;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

上記のコードを実行すると、入力除数が 0 の場合、div 関数で例外がスローされますが、このときプログラムの実行フローは main 関数の catch ブロックに直接ジャンプして実行されます。その結果、Func 関数リソースで要求されたメモリが解放されません

この場合、まず Func 関数の div 関数でスローされた例外をキャプチャし、キャプチャ後に以前に要求されたメモリ リソースを解放してから、例外を再スローします。コードは次のとおりです。

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	int* p1 = new int;
    int* p2 = new int;
	try
	{
		cout << div() << endl;
	}
	catch (...)
	{
		delete p1;
        delete p2;
		throw;
	}
	delete p1;
    delete p2;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

new も例外をスローします。ここで p1 new が例外をスローすると、プログラムの実行フローは main 関数の catch ブロックに直接ジャンプして実行されますが、ここでは問題ありません。p1 に問題がないとして、p2 の new も例外をスローしたらどうなるでしょうか。もう一度ネストしてキャッチする必要がありますか? ?

void Func()
{
	int* p1 = new int;
	int* p2 = nullptr;
	try
	{
		int* p2 = new int;//p2的new可能会抛异常
		try
		{
			cout << div() << endl;
		}
		catch (...)
		{
			delete p1;
			delete p2;
			throw;
		}
	}
	catch (...)
	{
		delete p1;
		throw;
	}
	delete p1;
	delete p2;
}

この種のコードは、try ステートメントと catch ステートメントがクレイジーにネストされており、非常に低位に見えるため、この問題を解決するために、スマート ポインターが登場します。

補足: メモリ リークの分類 C/C++ プログラムでは、通常、メモリ リークの 2 つの側面に注意します。

  • ヒープ メモリ リーク: ヒープ メモリとは、プログラム実行中に必要に応じて malloc / calloc / realloc / new などを通じてヒープから割り当てられたメモリのことで、使用後は対応する free または delete を呼び出して削除する必要があります。プログラムの設計ミスによりメモリのこの部分が解放されなかった場合、この部分の領域は将来使用されなくなり、ヒープ リークが発生します。
  • システム リソースの漏洩: ソケット、ファイル記述子、パイプなど、プログラムによって使用されるシステムによって割り当てられたリソースを指します。これらは、対応する関数を使用して解放されず、システム リソースの無駄が発生し、重大な問題につながる可能性があります。システムパフォーマンスの低下とシステム実行の不安定化

メモリ リークは非常に一般的で、解決策は 2 つあります。 1.事前防止タイプ。スマートポインターなど 2. イベント後のエラーチェックタイプ。漏れ検出ツールなど

1.2 スマート ポインタを使用して解決する

スマート ポインターとは何ですか? ?

  • 要求されたメモリ空間を管理のために SmartPtr に渡します
  • SmartPtr オブジェクトを構築するときに、管理するメモリ空間を SmartPtr オブジェクトに渡します。
  • SmartPtr オブジェクトが消滅すると、SmartPtr のデストラクターは管理されたメモリ空間を自動的に解放します。
  • 例外が発生した場合、要求された領域は SmartPtr オブジェクトのライフサイクルとともに解放され、メモリ リークの問題はうまく解決されます。
  • SmartPtr オブジェクトをネイティブ ポインターのように使用するには、  *and演算子をオーバーロードする必要もあります。 ->

例えば:

//智能指针
template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}
	~SmartPtr()
	{
		cout << "delete: " << _ptr << endl;
		delete _ptr;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}

void Func()
{
	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(new int);
	cout << div() << endl;
}

int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

上記のコードでは、プログラムが正常に実行されて復帰した場合でも、何らかの原因で途中復帰した場合や例外が発生した場合でも、要求されたメモリ空間を SmartPtr オブジェクト sp1 と sp2 に引き渡して管理しています。返されると、SmartPtr オブジェクトのライフサイクルが終了する限り、対応するデストラクターが呼び出され、メモリ リソースの解放が完了します。

1.3 スマートポインタの原理

(1)ライ

RAII (Resource Acquisition Is Initialization) は、オブジェクトのライフサイクルを使用してプログラム リソース(メモリ、ファイル ハンドル、ネットワーク接続、ミューテックスなど) を制御する単純な手法です。

オブジェクトの構築時にリソースを取得し、オブジェクトの存続期間中有効なままになるようにリソースへのアクセスを制御し、最後にオブジェクトが破棄されるときにリソースを解放します。このようにして、実際にはリソースを管理する責任をオブジェクトに委ねます。

このアプローチには次の 2 つの利点があります。

  1. リソースを明示的に解放する必要はありません。
  2. このようにして、オブジェクトが必要とするリソースは、その存続期間を通じて有効なままになります。

例えば:

// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}
private:
	T* _ptr;
};

(2) ポインタのような動作

上記(1)のSmartPtrは、まだポインタとしての動作を持っていないため、スマートポインタとは言えません。ポインタは逆参照でき、 -> を介してポイントされた空間のコンテンツにアクセスすることもできるため、スマート ポインタでは、ポインタのように使用できるように、* と ->をオーバーロードする必要があります。

template<class T>
class SmartPtr {
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}
	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
private:
	T* _ptr;
};

スマート ポインターの原理を要約します。

  1. RAII特性
  2. 演算子* と演算子-> をポインタのような動作でオーバーロードします。 

しかし、そのようなスマート ポインターは十分に完璧ではなく、スマート ポインター オブジェクトのコピーの問題が発生するため、C++ にはさまざまなバージョンのスマート ポインターが存在します。

スマート ポインター オブジェクトのコピーの問題を解決します。たとえば、上記で実装されたスマート ポインター SmartPtr クラスでは、SmartPtr オブジェクトを使用して別の SmartPtr オブジェクトをコピーして構築したり、SmartPtr オブジェクトが別の SmartPtr オブジェクトに割り当てられたりすると、プログラムがクラッシュします。

int main()
{
	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(sp1); //拷贝构造

	SmartPtr<int> sp3(new int);
	SmartPtr<int> sp4(new int);
	sp3 = sp4; //拷贝赋值
	
	return 0;
}

理由:

  • コンパイラによって生成されるコピー コンストラクターは、デフォルトで組み込み型の値のコピー (浅いコピー) を完了するため、sp1 を使用して sp2 をコピーして構築した後、sp1 と sp2 が同じメモリ空間を管理することと同等になります。 sp1 と sp2 が破棄されると、このスペースは 2 回解放されます。
  • コンパイラがデフォルトで生成するコピー代入関数は、組み込み型の値のコピー(浅いコピー)も完了するため、sp4がsp3に代入された後は、sp3が管理する空間と同等となり、sp4が元の管理空間となります。 sp3 と sp4 が破棄されると、この領域は 2 回解放され、元々 sp4 が管理していた領域も解放されなくなります。

2、C++ スマート ポインター

2.1 std::auto_ptr

auto_ptr のスマート ポインターは、ライブラリの C++98 バージョンで提供されます。ドキュメントでは次のように紹介されています。

头文件:
#include <memory> 

auto_ptr は、管理権の譲渡を通じてスマート ポインターのコピーの問題を解決し、常に 1 つのオブジェクトだけがリソースを管理し、同じリソースが複数回解放されないようにします。

テストコードは次のとおりです。

int main()
{
	std::auto_ptr<int> ap1(new int(1));
	std::auto_ptr<int> ap2(ap1);
	*ap2 = 10;

	std::auto_ptr<int> ap3(new int(1));
	std::auto_ptr<int> ap4(new int(2));
	ap3 = ap4;

	return 0;
}

デバッグして表示するには:

オブジェクトの管理権が譲渡されると、そのオブジェクトを使用して元の管理リソースにアクセスできなくなるため、上記の sp1 や sp2 などのオブジェクトが一時停止されます。 2 つのオブジェクトを使用すると、プログラムが直接クラッシュするため、auto_ptr を使用する前にそのメカニズムを理解する必要があります。そうしないと、プログラムに問題が発生しやすくなります。

auto_ptr は失敗した設計であり、多くの企業が auto_ptr の使用を禁止することを明確に規定しています。

auto_ptr の単純なモック実装

namespace fy
{
	template<class T>
	class auto_ptr
	{
	public:
		//RAII
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}
		auto_ptr(auto_ptr<T>& sp)
			:_ptr(sp._ptr)
		{
			sp._ptr = nullptr;// 管理权转移
		}
		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			if (this != &ap)// 检测是否为自己给自己赋值
			{
				// 释放当前对象中资源
				if (_ptr)
					delete _ptr;
				// 转移ap中资源到当前对象中
				_ptr = ap._ptr;
				ap._ptr = NULL;
			}
			return *this;
		}
		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}
		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};
}

2.2 std::unique_ptr

unique_ptr は、C++11 で導入されたスマート ポインタです。unique_ptr は、アンチコピーによってスマート ポインタのコピー問題を解決します。つまり、スマート ポインタ オブジェクトのコピーを単純かつ無作法に防止し、リソースが解放されないようにします。複数回。

头文件:
#include <memory> 

ドキュメントの紹介: unique_ptr

 テストコード

int main()
{
	std::unique_ptr<int> up1(new int(1));
	std::unique_ptr<int> up2(up1); //error,不允许拷贝
	return 0;
}

コンパイルエラー

unique_ptr のシンプルなアナログ実装 

namespace fy
{
	template<class T>
	class unique_ptr
	{
	public:
		// RAII
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}
		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}
		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		//防拷贝
		unique_ptr(const unique_ptr<T>&sp) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>&sp) = delete;
	private:
		T* _ptr;
	};
}

 2.3 std::shared_ptr

shared_ptr は、C++11 で導入されたスマート ポインターです。shared_ptrは、スマート ポインターのコピー問題を参照カウントによって解決します。つまり、shared_ptr はコピーをサポートします。

头文件:
#include <memory> 

 ドキュメントの紹介: shared_ptr

 shared_ptr の原理: 参照カウントを使用して複数のshared_ptr オブジェクト間でリソースを共有する

  • shared_ptr は、内部の各リソースのカウントを維持します。これは、リソースが複数のオブジェクトによって共有されていることを記録するために使用されます。
  • オブジェクトが破棄される (つまり、デストラクターが呼び出される) と、リソースが使用されなくなったことを意味し、オブジェクトの参照カウントが 1 つ減ります。
  • 参照カウントが 0 の場合、それがリソースを使用する最後のオブジェクトであり、リソースを解放する必要があることを意味します。
  • 0 でない場合は、自分以外にもそのリソースを使用しているオブジェクトが存在することを意味し、リソースを解放できません。そうしないと、他のオブジェクトがワイルド ポインタになります。

この参照カウント方式により、複数のオブジェクトが特定のリソースをまとめて管理できるようになります。つまり、スマート ポインタのコピーがサポートされ、リソースに対応する参照カウントが 0 になった場合にのみリソースが解放されます。したがって、同じ A リソースが複数回解放されないことが保証されます。

テストコード:

// 引用计数支持多个拷贝管理同一个资源,最后一个析构对象释放资源
int main()
{
	shared_ptr<int> sp1(new int(1));
	shared_ptr<int> sp2(sp1);
	*sp1 = 2;
	*sp2 = 3;
	//use_count成员函数,用于获取当前对象管理的资源对应的引用计数
	cout << sp1.use_count() << endl;

	shared_ptr<int> sp3(new int(1));
	shared_ptr<int> sp4(new int(2));
	shared_ptr<int> sp5(new int(3));
	sp4 = sp3;
	sp5 = sp3;
	cout << sp3.use_count() << endl;
	return 0;
}

演算結果

 

デバッグビュー

Shared_ptr 簡易シミュレーションの実装

  1. スマート ポインター オブジェクトによって管理されるリソースに対応する参照カウントを示すために、メンバー変数 count を追加する必要があります。
  2. コンストラクターでリソースを適用し、ヒープ上で開き、参照カウントを初期化して 1 に設定し、現在このリソースを管理しているオブジェクトが 1 つだけであることを示します。
  3. コピーコンストラクターでは、1回コピーする際に、同時にリソースに対応する参照数が++である必要があります
  4. コピー割り当て関数では、まず現在のオブジェクトが管理するリソースに対応する参照カウントをカウントし (0 になった場合は解放する必要があります)、次に、そのオブジェクトが管理するリソースを受信オブジェクトとともに管理します。同時に、リソース Count ++ に対応する参照も必要です。
  5. デストラクターでは、管理リソースに対応する参照カウント -- が 0 になった場合、リソースを解放する必要があります。
namespace fy
{
	template<class T>
	class shared_ptr
	{
	public:
		// (1)RAII
		shared_ptr(T* ptr)
			:_ptr(ptr)
			,_pCount(new int(1))
		{}
		~shared_ptr()
		{
			if (--(*_pCount) == 0)
			{
				if (_ptr != nullptr)
				{
					std::cout << "_ptr: " << _ptr << std::endl;
					delete _ptr;
					_ptr = nullptr;
				}
				delete _pCount;
				_pCount = nullptr;
			}
		}
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pCount(sp._pCount)
		{
			++(*_pCount);
		}

		shared_ptr<T>& operator=(shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)//管理同一块空间的对象之间无需进行赋值操作
			{
				if (--(*_pCount) == 0)
				{
					std::cout << "operator= delete: " << _ptr << std::endl;
					delete _ptr;
					delete _pCount;
				}
				_ptr = sp._ptr;
				_pCount = sp._pCount;
				++(*_pCount);
			}
			return *this;
		}
		//获取引用计数
		int use_count()
		{
			return *_pCount;
		}
		// (2)可以像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;//管理的资源
		int* _pCount; //管理的资源对应的引用计数
	};

テスト走行

 

注:shared_ptr の参照カウント count は、単に int 型のメンバー変数として定義することはできません。これは、各shared_ptr オブジェクトが独自の count メンバー変数を持つことを意味し、複数のオブジェクトが同じリソースを管理したい場合、これらのオブジェクトは同じ参照カウント

2.4shared_ptr スレッドの安全性の問題

上記でシミュレートされたshared_pt rにも、スレッドの安全性の問題があります。同じリソースを管理する複数のオブジェクトの参照カウントが共有されるため、複数のスレッドが同じ参照カウントを同時にインクリメントまたはデクリメントする可能性があります。自己インクリメント操作と自己デクリメント操作はどちらもアトミックではありません。そのため、参照カウントはロックによって保護する必要があります。そうしないと、スレッドの安全性の問題が発生します。

したがって、コード内の ++ -- 操作をロックする必要があります。

コードを次のように変更します。

#pragma once
#include <iostream>
#include <memory>
#include <mutex>

namespace fy
{
	template<class T>
	class shared_ptr
	{
	public:
		// (1)RAII
		shared_ptr(T* ptr)
			:_ptr(ptr)
			,_pCount(new int(1))
			, _mutex(new mutex)
		{}
		//对++操作进行加锁
		void Add()
		{
			_mutex->lock();
			(*_pCount)++;
			_mutex->unlock();
		}
		//对--操作进行加锁
		void Release()
		{
			_mutex->lock();
			bool flag = false;
			if (--(*_pCount) == 0) //将管理的资源对应的引用计数--
			{
				if (_ptr != nullptr)
				{
					cout << "delete: " << _ptr << endl;
					delete _ptr;
					delete _pCount;
					_ptr = nullptr;
					_pCount = nullptr;
					flag = true;
				}
			}
			_mutex->unlock();
			if (flag == true)//释放锁
			{
				delete _mutex;
			}
		}
		~shared_ptr()
		{
			Release();
		}
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pCount(sp._pCount)
			, _mutex(sp._mutex)
		{
			Add();
		}

		shared_ptr<T>& operator=(shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)//管理同一块空间的对象之间无需进行赋值操作
			{
				Release();
				_ptr = sp._ptr;
				_pCount = sp._pCount;
				_mutex = sp._mutex;
				Add();
			}
			return *this;
		}
		//获取引用计数
		int use_count()
		{
			return *_pCount;
		}
		// (2)可以像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;//管理的资源
		int* _pCount; //管理的资源对应的引用计数
		mutex* _mutex; //管理的资源对应的互斥锁
	};
}
  • Release 関数では、参照カウントが 0 になったときにミューテックス リソースを解放する必要がありますが、後でロック解除操作を行う必要があるため、クリティカル セクションではミューテックスを解放できません。そのため、フラグ変数を使用します。ロックを解除した後にミューテックス リソースを解放する必要性を判断するためのフラグ変数を介したコード
  • shared_ptr は、管理リソースのスレッド セーフではなく、参照カウントのスレッド セーフを確保するだけで済みます。
  • ヒープ上のリソースを指すスレッド セーフティの問題は、それにアクセスする人によって処理され、スマート ポインタはそれを気にせず、制御することもできません

スマート ポインターのカスタム デリーター

スマート ポインター オブジェクトのライフ サイクルが終了すると、すべてのスマート ポインターはdeleteデフォルトでリソースを解放しますが、これは適切ではありません。スマート ポインターはnewメソッドによって適用されるメモリ空間を管理するだけでなく、スマート ポインターが管理することも可能であるためです。new[]ウェイ内の領域を適用するか、ファイル ポインタを管理します。new[]ウェイ内に適用されたメモリ領域はdelete[]ウェイ内で解放する必要があり、ファイル ポインタはfclose関数を呼び出して解放する必要があります。

カスタム デリーターは、ファンクター、ラムダ式、または関数ポインターを通じて実装されます。ここで簡単に説明します。カスタム デリーターの実装は非常に複雑です。

2.5 std::weak_ptr

shared_ptr: 循環参照には致命的な欠陥があり、これを解決するためにweak_ptrが生成されます。weak_ptr は C++11 で導入されたスマート ポインターです。weak_ptr はリソースのリリースの管理には使用されません。weak_ptr はshared_ptr を補足するものです。

头文件:
#include <memory> 

ドキュメントの紹介: weak_ptr

循環参照問題

shared_ptr の循環参照の問題は、一部の特定のシナリオでのみ発生します。

たとえば、次のノード クラスを定義します。ヒープ上に 2 つの新しいノードを作成し、2 つのノードを接続し、最後に 2 つのノードを解放します。

struct ListNode
{
	ListNode* _next;
	ListNode* _prev;
	int _val;
	~ListNode(){ cout << "~ListNode()" << endl;}
};

int main()
{
	//新建节点
	ListNode* node1 = new ListNode;
	ListNode* node2 = new ListNode;

	node1->_next = node2;
	node2->_prev = node1;
	//释放
	delete node1;
	delete node2;
	return 0;
}

上記の手順で問題なく、両ノードとも正常に解放できます。プログラムの途中でリターンや例外スローなどの理由でノードが解放されることを防ぐため、2つのノードを2つのshared_ptrオブジェクトに渡して管理します。 ListNodeクラスもshared_ptr型に変更

struct ListNode
{
	std::shared_ptr<ListNode> _next;
	std::shared_ptr<ListNode> _prev;
	int _val;
	~ListNode() { cout << "~ListNode()" << endl; }
};

int main()
{
	//新建节点
	std::shared_ptr<ListNode> node1(new ListNode);
	std::shared_ptr<ListNode> node2(new ListNode);

	node1->_next = node2;
	node2->_prev = node1;
	return 0;
}

このとき、プログラム実行後に2つのノードは解放されませんが、ノードを接続する際の2行のコードのいずれかを削除すれば、正しく2つのノードが解放されます。根本的な理由は、この2行のおかげです。ノードを接続するコードにより循環参照が発生しました

循環参照分析:

  1. 2 つのスマート ポインター オブジェクト、node1 と node2 は 2 つのノードを指し、参照カウントは 1 になり、手動で削除する必要はありません。
  2. ノード 1 の _next はノード 2 を指し、ノード 2 の _prev はノード 1 を指し、参照カウントは 2 になります。
  3. Node1 と Node2 は破棄され、参照カウントは 1 に減りますが、_next は引き続き次のノードを指します。ただし、_prev は前のノードも指します。
  4. つまり、_next が破棄され、node2 が解放されます。
  5. つまり、_prev が破棄され、node1 が解放されます。
  6. しかし、_next はノードのメンバーであり、ノード 1 は解放され、_next は破棄され、ノード 1 は _prev によって管理され、_prev はノード 2 のメンバーであるため、これは循環参照と呼ばれ、誰も解放しません。

 

weak_ptr は C++11 で導入されたスマート ポインタです。weak_ptr はリソースの解放を管理するために使用されるのではなく、主にshared_ptr の循環参照問題を解決するために使用されます。

  • 原則:weak_ptr は、shared_ptr オブジェクトを使用したweak_ptr オブジェクトの構築をサポートします。構築されたweak_ptr オブジェクトとshared_ptr オブジェクトは同じリソースを管理しますが、このリソースに対応する参照カウントは増加しません。 

 参照カウントのシナリオでは、ノードの _prev と _next をweak_ptr に変更するだけです。原則として、weak_ptr の _next と _prev が異なる場合、node1->_next = node2; および node2->_prev = node1; になります。ノード1とノード2の参照カウント。

コードを次のように変更します。

struct ListNode
{
	std::weak_ptr<ListNode> _next;
	std::weak_ptr<ListNode> _prev;
	int _val;
	~ListNode() { cout << "~ListNode()" << endl; }
};

int main()
{
	//新建节点
	std::shared_ptr<ListNode> node1(new ListNode);
	std::shared_ptr<ListNode> node2(new ListNode);

	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	node1->_next = node2;
	node2->_prev = node1;
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	return 0;
}

コンパイルして実行すると、リソースが正常に解放されます

 

 weak_ptr 簡易シミュレーションの実装

また、shared_ptr は、管理するリソースを取得するための get 関数も提供し、weak_ptr を支援します。

T* get() const
{
	return _ptr;
}

weak_ptr シミュレーションの実装コードは次のとおりです。

  1. weak_ptr は、shared_ptr オブジェクトを使用したweak_ptr オブジェクトのコピーと構築をサポートし、構築中にshared_ptr オブジェクトによって管理されるリソースを取得します。
  2. weak_ptr は、shared_ptr オブジェクトのweak_ptr オブジェクトへのコピーと割り当て、および値の割り当て時にshared_ptr オブジェクトによって管理されるリソースの取得をサポートします。
template<class T>
	class weak_ptr
	{
	public:
		// RAII
		weak_ptr()
			:_ptr(nullptr)
		{}
		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())//weak_ptr支持用shared_ptr对象拷贝构造weak_ptr对象,构造时获取shared_ptr对象管理的资源。
		{}
		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();//weak_ptr支持用shared_ptr对象拷贝赋值给weak_ptr对象,赋值时获取shared_ptr对象管理的资源
			return *this;
		}
		// (2)可以像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};

3. C++11のスマートポインタとブーストの関係

  1. 最初のスマート ポインター auto_ptr は C++98 で作成されました。
  2. C++ ブーストでは、より実用的なscoped_ptr、shared_ptr、weak_ptr が提供されます。
  3. C++ TR1、shared_ptr の導入など。ただし、TR1 は標準バージョンではないことに注意してください。
  4. C++11 では、unique_ptr、shared_ptr、weak_ptr が導入されました。unique_ptr は boost のscoped_ptr に対応することに注意してください。これらのスマート ポインターの実装原則は、ブーストでの実装を参照しています。
  5. ブースト ライブラリは、C++ 言語標準ライブラリへの拡張機能を提供するいくつかの C++ ライブラリの一般的な用語です。ブースト ライブラリ コミュニティの本来の目的の 1 つは、C++ の標準化のためのリファレンス実装を提供することです。たとえば、C++ 標準ライブラリではTR1 はレビューに提出されており、標準ライブラリの候補となったブースト ライブラリは 10 個あります。

- - - - - - - - - - - 終わり - - - - - - - - - - - 

「 作者 」 枫叶先生
「 更新 」 2023.5.13
「 声明 」 余之才疏学浅,故所撰文疏漏难免,
          或有谬误或不准确之处,敬请读者批评指正。

Supongo que te gusta

Origin blog.csdn.net/m0_64280701/article/details/130331363
Recomendado
Clasificación