前回の記事では、スマートポインタの原理を説明し、簡単なスマートポインタを実装しました。スマート ポインターについての理解を深めるために、この記事では C++ のいくつかのスマート ポインター (auto_ptr、unique_ptr、shared_ptr、weak_ptr) について説明します。
1、auto_ptr
前回の記事では、smart_ptr のコピー コンストラクターと代入演算子を無効にしました。auto_ptr はコピー コンストラクターと代入演算子でどのように処理されますか? 元のポインタを nullptr に割り当てます。
template<class T>
class auto_ptr {
public:
auto_ptr(T* _ptr) : ptr(_ptr) {
}
auto_ptr(auto_ptr& _ap) : ptr(_ap.ptr) {
_ap.ptr = nullptr;
}
auto_ptr<T>& operator = (auto_ptr<T>& _ap) {
if (ptr != _ap.ptr) {
ptr = _ap.ptr;
_ap.ptr = nullptr;
}
return *this;
}
~auto_ptr() {
delete ptr;
ptr = nullptr;
}
T& operator * () {
return *ptr;
}
T* operator -> () {
return ptr;
}
private:
T* ptr;
};
3、unique_ptr
unique_ptr は、C++11 バージョンのライブラリで提供されるスマート ポインターで、コピー コンストラクターと割り当てのオーバーロード関数を直接無効にします。したがって、 unique_ptr は移動のみ可能であり、割り当てはできません。
4、shared_ptr
shared_ptr を使用すると、複数のスマート ポインターが同じリソースを指すことができ、共有リソースが 1 回だけ解放されることが保証されます。
shared_ptr は、参照カウントの原則を使用して、複数のshared_ptr オブジェクト間のリソース共有を実現します。
(1) 参照カウントは、リソースが複数のオブジェクトによって共有されていることを記録するために使用されます。
(2)shared_ptr オブジェクトが破棄される(デストラクタが呼び出される)と、デストラクタ内でカウントが 1 減算されます。
(3) 参照カウントが 0 に減った場合、それがリソースを使用する最後のshared_ptr オブジェクトであり、リソースを解放する必要があることを意味します。
(4) 参照カウントが 0 でない場合は、他に使用中のオブジェクトがあるため、リソースを解放できません。
shared_ptr を使用する場合は、2 つのshared_ptr が同じ生ポインタを指さないように注意してください。次に例を示します。
A* p = new A(10);
shared_ptr<A> sp1(p), sp2(p);
sp1 と sp2 は p の同じ参照カウントを共有しませんが、それぞれが p の参照カウントを 1 として記録します (sp2 は、p が sp1 によって管理されていることを認識できません)。このようにして、sp1 が死ぬと p は破壊され、sp2 が死ぬと p は再び破壊され、プログラムがクラッシュします。
5、weak_ptr
weak_ptr クラスのオブジェクトはshared_ptrを指すことができますが、shared_ptrの参照カウントは変更されません。最後のshared_ptrが破棄されると、オブジェクトは解放されます。
weak_ptr は、operator-> 演算子と Operator * 演算子をオーバーロードしないため、weak_ptr を介してオブジェクトを直接使用することはできません。一般的な使用法は、lock 関数を呼び出してshared_ptr サンプルを取得し、元のオブジェクトにアクセスすることです。
shared_ptr 相互参照の例を見てみましょう。
class B;
class A
{
public:
shared_ptr<B> sp;
~A(){
cout << "~A"<<endl;
}
};
class B
{
public:
shared_ptr<A> sp;
~B(){
cout << "~B"<<endl;
}
};
void fun() {
shared_ptr<B> pb(new B());
shared_ptr<A> pa(new A());
pb->sp = pa;
cout << "pb.use_count " << pb.use_count() << endl;//1
cout << "pa.use_count " << pa.use_count() << endl;//2
pa->sp = pb;
cout << "pb.use_count " << pb.use_count() << endl;//2
cout << "pa.use_count " << pa.use_count() << endl;//2
//并没有输出 ~A, ~B,也就是class B;
class A
{
public:
shared_ptr<B> sp;
~A(){
cout << "~A"<<endl;
}
};
class B
{
public:
shared_ptr<A> sp;
~B(){
cout << "~B"<<endl;
}
};
void fun() {
shared_ptr<B> pb(new B());
shared_ptr<A> pa(new A());
pb->sp = pa;
cout << "pb.use_count " << pb.use_count() << endl;//1
cout << "pa.use_count " << pa.use_count() << endl;//2
pa->sp = pb;
cout << "pb.use_count " << pb.use_count() << endl;//2
cout << "pa.use_count " << pa.use_count() << endl;//2
//没有输出~A, ~B。也就是没有调用A和B的析构函数。
}
この相互参照を回避するにはどうすればよいでしょうか? これには、weak_ptr の使用が必要です。A のshared_ptr<B> sp をweak_ptr<B> sp_weak に変更します。これにより、sp 参照カウント use_count() の値が渡されるときに増加しないため、A と B のリソースは次のようになります。通常通りリリースされます:
class B;
class A
{
public:
//shared_ptr<B> sp;
weak_ptr<B> sp_weak;
~A(){
cout << "~A"<<endl;
}
};
class B
{
public:
shared_ptr<A> sp;
~B(){
cout << "~B"<<endl;
}
};
void fun() {
shared_ptr<B> pb(new B());
shared_ptr<A> pa(new A());
pb->sp = pa;
cout << "pb.use_count " << pb.use_count() << endl;//1
cout << "pa.use_count " << pa.use_count() << endl;//2
//pa->sp = pb;
pa->sp_weak = pb;
cout << "pb.use_count " << pb.use_count() << endl;//1
cout << "pa.use_count " << pa.use_count() << endl;//2
shared_ptr<B> pa2 = pa->sp_weak.lock();
}