を使用する場合、いくつかのエッジケースがあるようですenabled_shared_from_this
。例えば:
shared_from_this
を使用せずに実装できますenable_shared_from_this
か? もしそうなら、それは速くすることができますか?
を使用する場合、いくつかのエッジケースがあるようですenabled_shared_from_this
。例えば:
shared_from_this
を使用せずに実装できますenable_shared_from_this
か? もしそうなら、それは速くすることができますか?
Ashared_ptr
は3つです。それは参照カウンターであり、破壊者であり、所有するリソースです。
するとmake_shared
、3つすべてが一度に割り当てられ、その1つのブロックに構築されます。
shared_ptr<T>
からを作成する場合T*
、参照カウンター/デストロイヤーを個別に作成し、所有するリソースがT*
.
の目標は、基本的に (存在するという仮定の下で)から ashared_from_this
を抽出できるようにすることです。shared_ptr<T>
T*
すべての共有ポインタが 経由で作成された場合make_shared
、これは簡単です (失敗時の動作を定義したい場合を除く)。レイアウトは簡単です。
ただし、すべての共有ポインタがそのように作成されるわけではありません。std
ライブラリ関数によって作成されていないオブジェクトへの共有ポインターを作成できる場合がT*
あるため、共有ポインターの参照カウントおよび破棄データとは無関係です。
aまたは what が指している (一般に) そのような構造を見つける余地がないため、外部に格納する必要があります。これは、グローバルな状態とスレッド セーフのオーバーヘッドやその他の苦痛を意味します。T*
これは、 を必要としない人にとっては負担になり、必要shared_from_this
な人 (ミューテックス、ルックアップなど) にとっては、現在の状態と比較してパフォーマンスが低下します。
現在の設計では、 が に格納weak_ptr<T>
されenable_shared_from_this<T>
ます。これは、またはctor が呼び出されるweak_ptr
たびに初期化されます。から継承することで、クラス内に「スペースを作った」ため、からを作成できます。make_shared
shared_ptr<T>
shared_ptr<T>
T*
enable_shared_from_this<T>
これも非常に低コストで、単純なケースをうまく処理できます。weak_ptr<T>
最終的に、a のベースライン コストを 1 上回るオーバーヘッドが発生しT
ます。
2 つの異なる がある場合shared_from_this
、それらのweak_ptr<A>
とweak_ptr<B>
メンバーは無関係であるため、結果のスマート ポインターをどこに格納するか (おそらく両方?) があいまいになります。このあいまいさにより、エラーが発生します。これはweak_ptr<?>
、1 つの一意のメンバーが 1 つだけであると想定し、shared_from_this<?>
実際には 2 つあるためです。
リンクされたソリューションは、これを拡張する賢い方法を提供します。と書いてありますenable_shared_from_this_virtual<T>
。
weak_ptr<T>
ここでは、 を格納する代わりに、weak_ptr<Q>
がQ
の仮想基底クラスであるを格納し、仮想基底クラスでenable_shared_from_this_virtual<T>
一意に格納します。次に、非仮想オーバーライドおよび同様のメソッドを使用して、「メンバー ポインターまたは子型コンストラクター」を使用shared_from_this
する場合と同じインターフェイスを提供します。ここでは、タイプ セーフな方法で、参照カウント/デストロイヤー コンポーネントを所有リソース コンポーネントから分割します。shared_from_this<T>
shared_ptr
ここでのオーバーヘッドは basic よりも大きくなりshared_from_this
ます。仮想継承があり、仮想デストラクタを強制します。これは、オブジェクトが仮想関数テーブルへのポインタを格納しshared_from_this
、仮想関数テーブルのディスパッチが必要なため、アクセスが遅くなることを意味します。
利点は、「すぐに使える」ことです。shared_from_this<?>
階層内に一意のものが 1 つになりT
、shared_from_this<T>
.
はい、タイプのグローバルハッシュテーブルを使用できます
unordered_map< T*, weak_ptr<T> >
から共有ポインタのルックアップを実行しますthis
。
#include <memory>
#include <iostream>
#include <unordered_map>
#include <cassert>
using namespace std;
template<class T>
struct MySharedFromThis {
static unordered_map<T*, weak_ptr<T> > map;
static std::shared_ptr<T> Find(T* p) {
auto iter = map.find(p);
if(iter == map.end())
return nullptr;
auto shared = iter->second.lock();
if(shared == nullptr)
throw bad_weak_ptr();
return shared;
}
};
template<class T>
unordered_map<T*, weak_ptr<T> > MySharedFromThis<T>::map;
template<class T>
struct MyDeleter {
void operator()(T * p) {
std::cout << "deleter called" << std::endl;
auto& map = MySharedFromThis<T>::map;
auto iter = map.find(p);
assert(iter != map.end());
map.erase(iter);
delete p;
}
};
template<class T>
shared_ptr<T> MyMakeShared() {
auto p = shared_ptr<T>(new T, MyDeleter<T>());
MySharedFromThis<T>::map[p.get()] = p;
return p;
}
struct Test {
shared_ptr<Test> GetShared() { return MySharedFromThis<Test>::Find(this); }
};
int main() {
auto p = MyMakeShared<Test>();
assert(p);
assert(p->GetShared() == p);
}
ただし、shared_ptr が T* から構築されるたびにマップを更新する必要があり、deleter が呼び出される前に時間がかかります。また、スレッドセーフにするために、mutex はマップへのアクセスを保護し、スレッド間で同じタイプの割り当てをシリアル化する必要があります。したがって、この実装は のようには機能しませんenable_shared_from_this
。
アップデート:
make_shared で使用されるのと同じポインター トリックを使用してこれを改善すると、shared_from_this と同じくらい高速になるはずの実装がここにあります。
template<class T>
struct Holder {
weak_ptr<T> weak;
T value;
};
template<class T>
Holder<T>* GetHolder(T* p) {
// Scary!
return reinterpret_cast< Holder<T>* >(reinterpret_cast<char*>(p) - sizeof(weak_ptr<T>));
}
template<class T>
struct MyDeleter
{
void operator()(T * p)
{
delete GetHolder(p);
}
};
template<class T>
shared_ptr<T> MyMakeShared() {
auto holder = new Holder<T>;
auto p = shared_ptr<T>(&(holder->value), MyDeleter<T>());
holder->weak = p;
return p;
}
template<class T>
shared_ptr<T> MySharedFromThis(T* self) {
return GetHolder(self)->weak.lock();
}