なぜスマート ポインターが必要なのでしょうか?

なぜスマート ポインターが必要なのでしょうか?

  1. メモリ解放忘れによるメモリリークの問題を解決します。
  2. 例外的なセキュリティ問題を解決します。
#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()
{
    
    
	// 1、如果p1这里new 抛异常会如何?
	// 2、如果p2这里new 抛异常会如何?
	// 3、如果div调用这里又会抛异常会如何?
	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;
}

質問:p1ここで new が例外をスローするとどうなりますか?

回答:p1、p2スペースを開けず、メモリも解放されません。

質問:p2ここで new が例外をスローするとどうなりますか?

回答:p2スペースを開けず、メモリも解放されていません。

質問: div がこれを呼び出して例外をスローした場合はどうなりますか?

回答: メモリは解放されません。

では、どうすれば解決できるでしょうか?

スマート ポインターを使用すると、この問題を解決できます。

スマート ポインターの使用法と原理

ライ

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

オブジェクトの構築時にリソースを取得し、オブジェクトのライフサイクル中有効なままになるようにリソースへのアクセスを制御し、最後にオブジェクトが破棄されるときにリソースを解放します。これにより、リソースを管理する責任を実際にオブジェクトに委任します。このアプローチには、次の 2 つの大きな利点があります。

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

クラスを使用してこのポインターをカプセル化し、次のように実装します。

#include<iostream>
using namespace std;

namespace hayaizo
{
    
    
	template<class T>
	class smart_ptr
	{
    
    
	public:
		smart_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{
    
    }

		~smart_ptr()
		{
    
    
			if (_ptr)
			{
    
    
				cout << "delete: " << _ptr << endl;
				delete _ptr;
			}
		}
	private:
		T* _ptr;
	};
}

int div()
{
    
    
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
    
    
	hayaizo::smart_ptr<int> sp1(new int);
	hayaizo::smart_ptr<int> sp2(new int);
	cout << div() << endl;
}

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

	return 0;
}

操作結果:

これはポインターであるため、これら 2 つのシンボルの逆参照*->オーバーロードもサポートする必要があります。

namespace hayaizo
{
    
    
	template<class T>
	class smart_ptr
	{
    
    
	public:
		smart_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{
    
    }

		~smart_ptr()
		{
    
    
			if (_ptr)
			{
    
    
				cout << "delete: " << _ptr << endl;
				delete _ptr;
			}
		}

		T& operator*()
		{
    
    
			return *_ptr;
		}

		T* operator->()
		{
    
    
			return _ptr;
		}
	private:
		T* _ptr;
	};
}

しかし、このカプセル化には致命的な欠陥があり、アドレスはスマート ポインタでしか指すことができず、そうでないと同じメモリを 2 回解放する問題が発生するため、公式ライブラリがauto_ptrどのように解決しているかを見てみましょう。

ご覧のとおり、sp1のアドレスはsp2のアドレスになり、その後、sp1アドレスは になりますnullptr

目立たないバージョンを手でこすりますauto_ptr

namespace hayaizo
{
    
    
	template<class T>
	class auto_ptr
	{
    
    
	public:
		auto_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{
    
    }

		auto_ptr(auto_ptr<T>&ap)
			:_ptr(ap._ptr)
		{
    
    
			ap._ptr = nullptr;
		}

		auto_ptr<T> operator=(auto_ptr<T>& ap)
		{
    
    
			if (this != &ap)
			{
    
    
				if (_ptr)
				{
    
    
					delete _ptr;
				}

				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
			return *this;
		}

		~auto_ptr()
		{
    
    
			if (_ptr)
			{
    
    
				cout << "delete: " << _ptr << endl;
				delete _ptr;
			}
		}

		T& operator*()
		{
    
    
			return *_ptr;
		}

		T* operator->()
		{
    
    
			return _ptr;
		}
	private:
		T* _ptr;
	};
}

しかし、これは確かに問題を解決できますが、ネイティブ ポインタの機能が失われています。ネイティブ ポインタは、多くのポインタが指す同じアドレスをサポートします。解決策を導入する前に導入する必要がありますunique_ptr

unique_ptr

unique_ptrこれは非常に失礼です。直接コピーすることはできず、コピー構造は無効になっています。

	template<class T>
	class unique_ptr
	{
    
    
	public:
		unique_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{
    
    }

		unique_ptr(unique_ptr<T>& ap)=delete
		{
    
    }

		unique_ptr<T> operator=(unique_ptr<T>& ap)=delete
		{
    
    }

		~unique_ptr()
		{
    
    
			if (_ptr)
			{
    
    
				cout << "delete: " << _ptr << endl;
				delete _ptr;
			}
		}

		T& operator*()
		{
    
    
			return *_ptr;
		}

		T* operator->()
		{
    
    
			return _ptr;
		}
	private:
		T* _ptr;
	};

共有_ptr

shared_ptr の原理は、参照カウントを通じて複数のshared_ptr オブジェクト間のリソース共有を実現することです。

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

たとえば、3 つのスマート ポインターを使用して address を指すようにする0x00112233と、カウントは 3 になり、カウントが 0 になると破棄されます。

では、ここでのカウントは単純にint _cntまたはで表すことができますかstatic int _cnt?

答えは否定的です。

そうである場合int _cnt、各オブジェクトは個別にカウントされます。

「static int _cnt」の場合、各オブジェクトに同じカウントが使用されます。

したがって、カウントを表すにはポインターが必要です。

template<class T>
	class shared_ptr
	{
    
    
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_cnt(new int(1))
		{
    
    }

		shared_ptr(shared_ptr<T>& ap)
			:_ptr(ap._ptr)
			,_cnt(ap._cnt)
		{
    
    }

		void Release()
		{
    
    
			if (--(*_cnt) == 0)
			{
    
    
				cout << "delete: " << _ptr << endl;
				delete _ptr;
			}
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
    
    
			//p1=p1的情况
			if (_ptr == sp._ptr)
			{
    
    
				return *this;
			}

			Release();
			
			_ptr = sp._ptr;
			_cnt = sp._cnt;
			(*_cnt)++;
			return *this;
		}

		int use_count()
		{
    
    
			return *_cnt;
		}

		T* get() const
		{
    
    
			return _ptr;
		}

		T& operator*()
		{
    
    
			return *_ptr;
		}

		T* operator->()
		{
    
    
			return _ptr;
		}

		~shared_ptr()
		{
    
    
			Release();
		}

	private:
		T* _ptr;
		int* _cnt;
	};

}

循環参照

ここでのn1合計の参照カウントはn2両方とも 2 であるため、相互制約の状況が形成されます。

n1破壊を見てくださいn2n2破壊を見てくださいn1

弱い_ptr

weak_ptr は、shared_ptr と連携するために導入されたスマート ポインターです。

weak_ptr は、shared_ptr または別のweak_ptr オブジェクトから構築できます。その構築と破棄によって、shared_ptr 参照カウントが増減することはありません。

	template<class T>
	class weak_ptr
	{
    
    
	public:
		weak_ptr()
			:_ptr(nullptr)
		{
    
    }

		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{
    
    }

		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
    
    
			_ptr = sp.get();
			return *this;
		}

		T& operator*()
		{
    
    
			return *_ptr;
		}

		T* operator->()
		{
    
    
			return _ptr;
		}

	private:
		T* _ptr;
	};

struct Node
{
    
    
	int _val;

	hayaizo::weak_ptr<Node> _next;
	hayaizo::weak_ptr<Node> _prev;

	~Node()
	{
    
    
		cout << "~Node" << endl;
	}
};


int main(void)
{
    
    
	hayaizo::shared_ptr<Node> n1(new Node);
	hayaizo::shared_ptr<Node> n2(new Node);
	
	n1->_next = n2;
	n2->_prev = n1;


	return 0;
}

これは非常に単純で、n1内部ポインタは参照カウントに関与せず、別のクラスでカプセル化するだけで、shared_ptr<T>内部の参照カウントには影響しません。

カスタムデリーター

実際、これはファンクターであり、削除計画を自分で渡すことができます。

//默认删除器
	template<class T>
	struct Delete
	{
    
    
		void operator()(T* ptr)
		{
    
    
			cout << "delete: " << ptr << endl;
			delete ptr;
		}
	};

	template<class T,class D=Delete<T>>
	class shared_ptr
	{
    
    
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_cnt(new int(1))
		{
    
    }

		shared_ptr(shared_ptr<T>& ap)
			:_ptr(ap._ptr)
			,_cnt(ap._cnt)
		{
    
    }

		void Release()
		{
    
    
			if (--(*_cnt) == 0)
			{
    
    
				cout << "delete: " << _ptr << endl;
				D del;
				del(_ptr);
				//D()(_ptr);匿名对象调用()
			}
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
    
    
			//p1=p1的情况
			if (_ptr == sp._ptr)
			{
    
    
				return *this;
			}

			Release();
			
			_ptr = sp._ptr;
			_cnt = sp._cnt;
			(*_cnt)++;
			return *this;
		}

		int use_count()
		{
    
    
			return *_cnt;
		}

		T* get() const
		{
    
    
			return _ptr;
		}

		T& operator*()
		{
    
    
			return *_ptr;
		}

		T* operator->()
		{
    
    
			return _ptr;
		}

		~shared_ptr()
		{
    
    
			Release();
		}

	private:
		T* _ptr;
		int* _cnt;
	};
template<class T>
struct DeleteArray
{
    
    
	void operator()(T* ptr)
	{
    
    
		cout << "delete[]" << ptr << endl;
		delete[] ptr;
	}
};

template<class T>
struct Free
{
    
    
	void operator()(T* ptr)
	{
    
    
		cout << "free" << ptr << endl;
		free(ptr);
	}
}; 

合計コード:

//#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()
//{
    
    
//	// 1、如果p1这里new 抛异常会如何?
//	// 2、如果p2这里new 抛异常会如何?
//	// 3、如果div调用这里又会抛异常会如何?
//	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;
//}

#include<iostream>
using namespace std;

namespace hayaizo
{
    
    
	template<class T>
	class auto_ptr
	{
    
    
	public:
		auto_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{
    
    }

		auto_ptr(auto_ptr<T>&ap)
			:_ptr(ap._ptr)
		{
    
    
			ap._ptr = nullptr;
		}

		auto_ptr<T> operator=(auto_ptr<T>& ap)
		{
    
    
			if (this != &ap)
			{
    
    
				if (_ptr)
				{
    
    
					delete _ptr;
				}

				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
			return *this;
		}

		~auto_ptr()
		{
    
    
			if (_ptr)
			{
    
    
				cout << "delete: " << _ptr << endl;
				delete _ptr;
			}
		}

		T& operator*()
		{
    
    
			return *_ptr;
		}

		T* operator->()
		{
    
    
			return _ptr;
		}
	private:
		T* _ptr;
	};

	template<class T>
	class unique_ptr
	{
    
    
	public:
		unique_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{
    
    }

		unique_ptr(unique_ptr<T>& ap)=delete
		{
    
    }

		unique_ptr<T> operator=(unique_ptr<T>& ap)=delete
		{
    
    }

		~unique_ptr()
		{
    
    
			if (_ptr)
			{
    
    
				cout << "delete: " << _ptr << endl;
				delete _ptr;
			}
		}

		T& operator*()
		{
    
    
			return *_ptr;
		}

		T* operator->()
		{
    
    
			return _ptr;
		}
	private:
		T* _ptr;
	};

	//默认删除器
	template<class T>
	struct Delete
	{
    
    
		void operator()(T* ptr)
		{
    
    
			cout << "delete: " << ptr << endl;
			delete ptr;
		}
	};

	template<class T,class D=Delete<T>>
	class shared_ptr
	{
    
    
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_cnt(new int(1))
		{
    
    }

		shared_ptr(shared_ptr<T>& ap)
			:_ptr(ap._ptr)
			,_cnt(ap._cnt)
		{
    
    }

		void Release()
		{
    
    
			if (--(*_cnt) == 0)
			{
    
    
				cout << "delete: " << _ptr << endl;
				D del;
				del(_ptr);
				//D()(_ptr);匿名对象调用()
			}
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
    
    
			//p1=p1的情况
			if (_ptr == sp._ptr)
			{
    
    
				return *this;
			}

			Release();
			
			_ptr = sp._ptr;
			_cnt = sp._cnt;
			(*_cnt)++;
			return *this;
		}

		int use_count()
		{
    
    
			return *_cnt;
		}

		T* get() const
		{
    
    
			return _ptr;
		}

		T& operator*()
		{
    
    
			return *_ptr;
		}

		T* operator->()
		{
    
    
			return _ptr;
		}

		~shared_ptr()
		{
    
    
			Release();
		}

	private:
		T* _ptr;
		int* _cnt;
	};

	template<class T>
	class weak_ptr
	{
    
    
	public:
		weak_ptr()
			:_ptr(nullptr)
		{
    
    }

		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{
    
    }

		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
    
    
			_ptr = sp.get();
			return *this;
		}

		T& operator*()
		{
    
    
			return *_ptr;
		}

		T* operator->()
		{
    
    
			return _ptr;
		}

	private:
		T* _ptr;
	};

}

struct Node
{
    
    
	int _val;

	hayaizo::weak_ptr<Node> _next;
	hayaizo::weak_ptr<Node> _prev;

	~Node()
	{
    
    
		cout << "~Node" << endl;
	}
};

template<class T>
struct DeleteArray
{
    
    
	void operator()(T* ptr)
	{
    
    
		cout << "delete[]" << ptr << endl;
		delete[] ptr;
	}
};

template<class T>
struct Free
{
    
    
	void operator()(T* ptr)
	{
    
    
		cout << "free" << ptr << endl;
		free(ptr);
	}
}; 


int main(void)
{
    
    
	/*hayaizo::shared_ptr<Node> n1(new Node);
	hayaizo::shared_ptr<Node> n2(new Node);
	
	n1->_next = n2;
	n2->_prev = n1;*/
	hayaizo::shared_ptr<Node, DeleteArray<Node>> n1(new Node[5]);
	hayaizo::shared_ptr<Node> n2(new Node);
	hayaizo::shared_ptr<int, DeleteArray<int>> n3(new int[5]);
	hayaizo::shared_ptr<int, Free<int>> n4((int*)malloc(sizeof(int)));


	return 0;
}

おすすめ

転載: blog.csdn.net/AkieMo/article/details/132252552