std::shared_ptr
| ヘッダ <memory> で定義
|
||
template< class T > class shared_ptr; |
(C++11以上) | |
std::shared_ptr はポインタを通してオブジェクトの共有所有権を保持するスマートポインタです。 複数の shared_ptr オブジェクトが同じオブジェクトを所有できます。 以下のいずれかの状況が発生するとオブジェクトは破棄され、メモリは解放されます。
- そのオブジェクトを所有する最後の
shared_ptrが破棄された場合 - そのオブジェクトを所有する最後の
shared_ptrに operator= または reset() を通して他のポインタが代入された場合
オブジェクトは delete 式または構築中に shared_ptr に供給されたカスタムデリータを使用して破棄されます。
shared_ptr はポインタを他のオブジェクトに格納することでオブジェクトの所有権を共有できます。 この機能は他のオブジェクトが所有しているオブジェクトを指すために使用することができます。 格納されたポインタは get()、逆参照演算子、比較演算子によってアクセスされます。 管理対象のポインタは使用カウントがゼロに達するとデリータに渡されます。
shared_ptr はオブジェクトを所有しないこともあります。 この場合は空と呼ばれます (作成時にエイリアシングコンストラクタが使用された場合、空の shared_ptr が非ヌルポインタを持つ場合もあります)。
shared_ptr のすべての特殊化は CopyConstructible, CopyAssignable, LessThanComparable の要件を満たし、 bool に文脈的に変換できます。
すべてのメンバ関数 (コピーコンストラクタおよびコピー代入演算子を含む) は、同じオブジェクトの所有権を共有している場合でも、追加の同期を行わずとも、 shared_ptr の異なるインスタンスに対しては複数のスレッドで呼ぶことができます。 複数のスレッドが同期せずに同じ shared_ptr にアクセスし、これらのアクセスのいずれかが shared_ptr の非 const メンバ関数を使用する場合は、データ競合が発生します。 アトミック関数の shared_ptr オーバーロードを使用すると、データ競合を避けることができます。
メンバ型
| メンバ型 | 定義 | ||||
element_type
|
| ||||
weak_type (C++17以上)
|
std::weak_ptr<T>
|
メンバ関数
新しい shared_ptr を構築します (パブリックメンバ関数) | |
所有対象オブジェクトへリンクしている shared_ptr がなくなった場合、そのオブジェクトを破棄します (パブリックメンバ関数) | |
shared_ptr を代入します (パブリックメンバ関数) | |
変更 | |
| 管理対象オブジェクトを置き換えます (パブリックメンバ関数) | |
| 管理対象オブジェクトを入れ替えます (パブリックメンバ関数) | |
観察 | |
| 格納されているポインタを返します (パブリックメンバ関数) | |
| 格納されているポインタを逆参照します (パブリックメンバ関数) | |
(C++17) |
格納されている配列へのインデックスアクセスを提供します (パブリックメンバ関数) |
同じ管理対象オブジェクトを参照している shared_ptr オブジェクトの数を返します (パブリックメンバ関数) | |
(C++20未満) |
管理対象オブジェクトが現在の shared_ptr インスタンスによってのみ管理されているかどうか調べます (パブリックメンバ関数) |
| 格納されているポインタが非ヌルかどうか調べます (パブリックメンバ関数) | |
| shared_ptr のオーナーベースの順序付けを提供します (パブリックメンバ関数) | |
非メンバ関数
| 新しいオブジェクトを管理する shared_ptr を作成します (関数テンプレート) | |
| アロケータを使用して確保した新しいオブジェクトを管理する shared_ptr を作成します (関数テンプレート) | |
| 格納されているポインタに static_cast, dynamic_cast, const_cast または reinterpret_cast を適用します (関数テンプレート) | |
| 所有している場合、指定された型のデリータを返します (関数テンプレート) | |
(C++20で削除)(C++20で削除)(C++20で削除)(C++20で削除)(C++20で削除)(C++20) |
別の shared_ptr または nullptr と比較します (関数テンプレート) |
| 格納されているポインタの値を出力ストリームに出力します (関数テンプレート) | |
(C++11) |
std::swap アルゴリズムの特殊化 (関数テンプレート) |
| std::shared_ptr に対するアトミック操作の特殊化 (関数テンプレート) |
ヘルパークラス
(C++20) |
アトミックな共有ポインタ (クラステンプレートの特殊化) |
(C++11) |
std::shared_ptr に対するハッシュサポート (クラステンプレートの特殊化) |
推定ガイド(C++17以上)
ノート
オブジェクトの所有権は、別の shared_ptr からコピー構築またはコピー代入することによってのみ、別の shared_ptr と共有できます。 別の shared_ptr が所有しているベースとなる生のポインタを使用して新しい shared_ptr を構築することは未定義動作に繋がります。
std::shared_ptr は不完全型に対して使用することができます。 しかし、生のポインタに対するコンストラクタ (template<class Y> shared_ptr(Y*)) および template<class Y> void reset(Y*) メンバ関数は、完全型へのポインタに対してのみ呼ぶことができます (ちなみに std::unique_ptr は不完全型への生のポインタから構築することができます)。
std::shared_ptr<T> の T は関数型にできます。 この場合はオブジェクトポインタではなく関数へのポインタを管理します。 これはロードされた動的ライブラリやプラグインをその関数が参照されている限り維持するために使用することができます。
void del(void(*)()) {}
void fun() {}
int main(){
std::shared_ptr<void()> ee(fun, del);
(*ee)();
}
実装ノート
一般的な実装では、 std::shared_ptr は2つのポインタを保持します。
- 格納されているポインタ (
get()で返されるもの) - 制御ブロックへのポインタ
制御ブロックは以下のものを保持する動的確保されたオブジェクトです。
- 管理対象オブジェクトへのポインタまたは管理対象オブジェクトそのもの
- (型消去された) デリータ
- (型消去された) アロケータ
- 管理対象オブジェクトを所有している
shared_ptrの数 - 管理対象オブジェクトを参照している
weak_ptrの数
std::make_shared または std::allocate_shared を呼ぶことによって shared_ptr が作成された場合、制御ブロックと管理対象オブジェクトの両方のためのメモリが1回の確保によって作成されます。 管理対象オブジェクトは制御ブロックのデータメンバ内に直接構築されます。 shared_ptr のコンストラクタのいずれかを使用して shared_ptr が作成された場合、管理対象オブジェクトと制御ブロックは別々に確保されなければなりません。 この場合、制御ブロックは管理対象オブジェクトへのポインタを格納します。
shared_ptr によって直接保持されているポインタは get() によって返されるものであるのに対し、制御ブロックが保持しているポインタ (またはオブジェクト) は共有所有者の数がゼロに達したときに削除されるものです。 これらのポインタは等しくない場合があります。
shared_ptr のデストラクタは制御ブロックの共有所有者の数をデクリメントします。 このカウンタがゼロに達すると、制御ブロックは管理対象オブジェクトのデストラクタを呼びます。 制御ブロック自体は std::weak_ptr のカウンタが同様にゼロに達するまで解放されません。
既存の実装では、同じ制御ブロックへの shared_ptr が存在する場合、 weak_ptr の数はインクリメントされています ([1], [2])。
スレッド安全性の要件を満たすため、参照カウンタは一般的に std::memory_order_relaxed の std::atomic::fetch_add と同等の方法でインクリメントされます (デクリメントは、制御ブロックを安全に破棄するため、より強い順序付けが必要です)。
例
#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>
struct Base
{
Base() { std::cout << " Base::Base()\n"; }
// ノート: ここでは非仮想デストラクタで構いません。
~Base() { std::cout << " Base::~Base()\n"; }
};
struct Derived: public Base
{
Derived() { std::cout << " Derived::Derived()\n"; }
~Derived() { std::cout << " Derived::~Derived()\n"; }
};
void thr(std::shared_ptr<Base> p)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::shared_ptr<Base> lp = p; // スレッドセーフです。
// (たとえ共有されている use_count がインクリメントされていても)
{
static std::mutex io_mutex;
std::lock_guard<std::mutex> lk(io_mutex);
std::cout << "local pointer in a thread:\n"
<< " lp.get() = " << lp.get()
<< ", lp.use_count() = " << lp.use_count() << '\n';
}
}
int main()
{
std::shared_ptr<Base> p = std::make_shared<Derived>();
std::cout << "Created a shared Derived (as a pointer to Base)\n"
<< " p.get() = " << p.get()
<< ", p.use_count() = " << p.use_count() << '\n';
std::thread t1(thr, p), t2(thr, p), t3(thr, p);
p.reset(); // 所有権を main から解放します。
std::cout << "Shared ownership between 3 threads and released\n"
<< "ownership from main:\n"
<< " p.get() = " << p.get()
<< ", p.use_count() = " << p.use_count() << '\n';
t1.join(); t2.join(); t3.join();
std::cout << "All threads completed, the last one deleted Derived\n";
}
出力例:
Base::Base()
Derived::Derived()
Created a shared Derived (as a pointer to Base)
p.get() = 0xc99028, p.use_count() = 1
Shared ownership between 3 threads and released
ownership from main:
p.get() = (nil), p.use_count() = 0
local pointer in a thread:
lp.get() = 0xc99028, lp.use_count() = 3
local pointer in a thread:
lp.get() = 0xc99028, lp.use_count() = 4
local pointer in a thread:
lp.get() = 0xc99028, lp.use_count() = 2
Derived::~Derived()
Base::~Base()
All threads completed, the last one deleted Derived