web-dev-qa-db-ja.com

平等-std :: weak_ptrを比較

2つのstd :: weak_ptrまたは1つのstd :: weak_ptrと1つのstd :: shared_ptrを比較して同等性を確認したいと思います。

私が知りたいのは、weak_ptr/shared_ptrのそれぞれが指すオブジェクトが同じであるかどうかです。アドレスが一致しない場合だけでなく、基になるオブジェクトが削除された後、偶然同じアドレスで再構築された場合も、比較によって否定的な結果が得られるはずです。

したがって、基本的に、アロケータが同じアドレスを予約している場合でも、このアサーションを保持する必要があります。

auto s1 = std::make_shared<int>(43);
std::weak_ptr<int> w1(s1);

s1.reset();

auto s2 = std::make_shared<int>(41);
std::weak_ptr<int> w2(s2);

assert(!equals(w1,w2));

Weak_ptrテンプレートは等式演算子を提供しません、そして私が理解したようにそれは 正当な理由で です。

したがって、単純な実装は次のようになります。

template <typename T, typename U>
inline bool naive_equals(const std::weak_ptr<T>& t, const std::weak_ptr<U>& u)
{
    return !t.expired() && t.lock() == u.lock();
}

template <typename T, typename U>
inline bool naive_equals(const std::weak_ptr<T>& t, const std::shared_ptr<U>& u)
{
    return !t.expired() && t.lock() == u;
}

その間に最初のweak_ptrが期限切れになった場合、0になります。そうでない場合は、weak_ptrをshared_ptrにアップグレードし、アドレスを比較します。

これの問題は、weak_ptrを2回(1回)ロックする必要があることです!時間がかかりすぎるのではないかと思います。

私はこれを思いついた:

template <typename T, typename U>
inline bool equals(const std::weak_ptr<T>& t, const std::weak_ptr<U>& u)
{
    return !t.owner_before(u) && !u.owner_before(t);
}


template <typename T, typename U>
inline bool equals(const std::weak_ptr<T>& t, const std::shared_ptr<U>& u)
{
    return !t.owner_before(u) && !u.owner_before(t);
}

これは、uの所有者ブロックがtの「前」ではなく、tがuの前ではないかどうかをチェックするため、t == uです。

これは私が意図したとおりに機能しますか?別個のshared_ptrから作成された2つのweak_ptrは、常にこのように等しくないと比較されますか?それとも私は何かを逃しましたか?

編集:そもそもなぜこれをやりたいのですか?共有ポインターを持つコンテナーが必要であり、その中のオブジェクトへの参照を配布したいと思います。イテレータは無効になっている可能性があるため、使用できません。 (整数の)IDを配布することはできますが、それは一意性の問題につながり、マップタイプが必要になり、検索/挿入/削除操作が複雑になります。 std :: setを使用し、ポインター自体(ラッパークラスにカプセル化されている)をキーとして提供することで、クライアントはweak_ptrを使用してセット内のオブジェクトにアクセスできます。

30
fat-lobyte

私は完全に誤解したので、この答えを完全に書き直しました。これは正しく行うのが難しいことです!

標準と一致する_std::weak_ptr_および_std::shared_ptr_の通常の実装は、2つのヒープオブジェクト(管理対象オブジェクトと制御ブロック)を持つことです。同じオブジェクトを参照する各共有ポインターには、オブジェクトと制御ブロックへのポインターが含まれ、同様に各弱いポインターが含まれます。制御ブロックは、共有ポインターの数と弱ポインタ​​ーの数の記録を保持し、共有ポインターの数が0に達すると、管理対象オブジェクトの割り当てを解除します。弱ポインタ​​の数も0に達すると、制御ブロック自体の割り当てが解除されます。

これは、共有ポインターまたは弱いポインター内のオブジェクトポインターが、実際の管理対象オブジェクトのサブオブジェクトを指すことができるという事実によって複雑になります。基本クラス、メンバー、または管理対象オブジェクトが所有する別のヒープオブジェクト。

_S0 ----------______       MO <------+
   \__             `----> BC        |
      \_ _______--------> m1        |
     ___X__               m2 --> H  |
S1 -/      \__ __----------------^  |
    \___ _____X__                   |
    ____X________\__                |
W0 /----------------`---> CB -------+  
                          s = 2 
                          w = 1 
_

ここでは、管理対象オブジェクトの基本クラスとメンバーをそれぞれ指す2つの共有ポインターと、管理対象オブジェクトが所有するヒープオブジェクトを指す弱いポインターがあります。制御ブロックは、2つの共有ポインターと1つの弱いポインターが存在することを記録します。制御ブロックには、管理対象オブジェクトへのポインターもあります。このポインターを使用して、管理対象オブジェクトが期限切れになったときに削除します。

_owner_before_/_owner_less_セマンティクスは、共有ポインターと弱ポインタ​​ーを制御ブロックのアドレスで比較します。これは、ポインター自体が変更されない限り変更されないことが保証されています。弱いポインタexpiresすべての共有ポインタが破棄されたとしても、その制御ブロックは、すべての弱いポインタも破棄されるまで存在します。

したがって、equalsコードは完全に正しく、スレッドセーフです。

問題は、オブジェクトポインターを比較し、同じ制御ブロックを持つ2つの共有ポインターが(上記のように)異なるオブジェクトを指す可能性があるため、_shared_ptr::operator==_と整合性がないことです。

_shared_ptr::operator==_との一貫性を保つために、t.lock() == uの記述はまったく問題ありません。ただし、それがtrueを返す場合、弱いポインタが他の共有ポインタの弱いポインタであるかどうかはまだ明確ではないことに注意してください。 it couldエイリアスポインタであるため、次のコードで期限切れになる可能性があります。

ただし、制御ブロックを比較するとオーバーヘッドが少なくなり(制御ブロックを調べる必要がないため)、エイリアスポインターを使用していない場合は、_==_と同じ結果が得られます。


ここの基準には何か欠陥があると思います。 _owner_equals_と_owner_hash_を追加すると、順序付けられていないコンテナーで_weak_ptr_を使用できるようになり、_owner_equals_が与えられると、コントロールを安全に比較できるため、弱いポインターを比較することが実際に賢明になります。ブロックポインタthenオブジェクトポインタ。2つの弱いポインタが同じ制御ブロックを持っている場合、両方またはどちらも期限切れになっていないことがわかります。おそらく、標準の次のバージョンのための何か。

25
ecatmur