20

cppreference.comによると、std::shared_ptrは相対演算子の完全なセット(==、!=、<、...)を提供しますが、比較のセマンティクスは指定されていません。基になる生のポインターを参照されるオブジェクトと比較し、std::weak_ptrとstd::unique_ptrも同じことを行うと思います。

いくつかの目的のために、(それらへのポインターではなく)参照されたオブジェクトの比較に基づいてスマートポインターを順序付ける相対演算子が必要です。これはすでに私がよく行うことですが、相対演算子を除いてほとんど生のポインターのように動作する私自身の「ダムポインター」を使用しています。標準のC++11スマートポインターでも同じことをしたいと思います。それで...

  1. C ++ 11スマートポインター(shared_ptr、weak_ptr、unique_ptr)から継承し、相対演算子をオーバーライドしても大丈夫ですか?

  2. 注意が必要な卑劣な問題はありますか?たとえば、using物事が正しく機能することを保証するために実装または使用する必要がある他の方法はありますか?

  3. 究極の怠惰のために、これを自動的に行うライブラリテンプレートはありますか?

これが「もちろんできるよ、ばか!」だといいのですが。std::mapある種のことですが、標準ライブラリには、継承するはずのないクラス(少なくともコンテナなど)がいくつかあるため、少しわかりません。

4

3 に答える 3

11

最初に、他の人がすでに指摘しているように、継承は進むべき道ではないということです。しかし、受け入れられた回答で提案された複雑なラッパーではなく、もっと簡単なことをします。独自の型に独自のコンパレータを実装します。

namespace myns {
struct mytype {
   int value;
};
bool operator<( mytype const& lhs, mytype const& rhs ) {
   return lhs.value < rhs.value;
}
bool operator<( std::shared_ptr<mytype> const & lhs, std::shared_ptr<mytype> const & rhs )
{
   // Handle the possibility that the pointers might be NULL!!!
   // ... then ...
   return *lhs < *rhs;
}
}

まったく魔法ではない魔法は、Argument Dependent Lookup (別名、Koening Lookup または ADL)です。コンパイラが関数呼び出しに遭遇すると、引数の名前空間をルックアップに追加します。オブジェクトがテンプレートのインスタンス化である場合、コンパイラは、テンプレートのインスタンス化に使用される型の名前空間も追加します。だから:

int main() {
   std::shared_ptr<myns::mytype> a, b;
   if ( a < b ) {                       // [1]
      std::cout << "less\n";
   } else {
      std::cout << "more\n";
   }
}

[1] では、andabはオブジェクトのユーザー定義型 (*)であるため、 ADL が開始され、ルックアップ セットにstdと の両方が追加されます。myns次に、operator<forの標準的な定義std::shared_ptrは次のとおりです。

template<class T, class U>
bool std::operator<(shared_ptr<T> const& a, shared_ptr<U> const& b) noexcept;

また、追加mynsして追加します:

bool myns::operator<( mytype const& lhs, mytype const& rhs );

myns::operator<次に、ルックアップが終了した後、オーバーロードの解決が開始され、呼び出しよりも適切な一致であると判断さstd::operator<れます。これは完全な一致であり、その場合は非テンプレートが優先されるためです。operator<その後、標準の代わりに独自のものを呼び出します。

あなたのタイプが実際にテンプレートである場合、これはもう少し複雑になります。そうである場合は、コメントをドロップしてください。回答を拡張します。


(*)これは少し単純化したものです。operator<メンバー関数またはフリー関数の両方として実装できるため、コンパイラは内部std::shared_ptr<>でメンバーoperator<(標準には存在しない) とその仲間をチェックします。mytypeまた、関数の内部も調べfriendます...など。しかし、最終的には正しいものを見つけるでしょう。

于 2012-09-22T02:39:30.883 に答える
9

一般に、デストラクタが動的でないものから継承するのは安全ではありません。それは可能であり、一般的に行われていますが、本当に注意する必要があります. ポインターから継承する代わりに、特にメンバーの数が比較的少ないため、合成を使用します。このためのテンプレートクラスを作成できるかもしれません

template<class pointer_type>
class relative_ptr {
public:
    typedef typename std::pointer_traits<pointer_type>::pointer pointer;
    typedef typename std::pointer_traits<pointer_type>::element_type element_type;
    relative_ptr():ptr() {}
    template<class U>
    relative_ptr(U&& u):ptr(std::forward<U>(u)) {}
    relative_ptr(relative_ptr<pointer>&& rhs):ptr(std::move(rhs.ptr)) {}
    relative_ptr(const relative_ptr<pointer>& rhs):ptr(std::move(rhs.ptr)) {}

    void swap (relative_ptr<pointer>& rhs) {ptr.swap(rhs.ptr);}
    pointer release() {return ptr.release();}
    void reset(pointer p = pointer()) {ptr.reset(p);}
    pointer get() const {return ptr.get();}
    element_type& operator*() const {return *ptr;}
    const pointer_type& operator->() const {return ptr;}

    friend bool operator< (const relative_ptr& khs, const relative_ptr& rhs) const 
    {return std::less<element>(*lhs,*rhs);}
    friend bool operator<=(const relative_ptr& khs, const relative_ptr& rhs) const 
    {return std::less_equal<element>(*lhs,*rhs);}
    friend bool operator> (const relative_ptr& khs, const relative_ptr& rhs) const 
    {return std::greater<element>(*lhs,*rhs);}
    friend bool operator>=(const relative_ptr& khs, const relative_ptr& rhs) const 
    {return std::greater_equal<element>(*lhs,*rhs);}
    friend bool operator==(const relative_ptr& khs, const relative_ptr& rhs) const 
    {return *lhs==*rhs;}
    friend bool operator!=(const relative_ptr& khs, const relative_ptr& rhs) const 
    {return *lhs!=*rhs;}
protected:
    pointer_type ptr;
};

明らかに、ラッパーの単純さは、スマート ポインターの最小公分母にまであなたを減らします。厳密には複雑ではありません。スマート ポインター クラスごとに 1 つ作成できます。

==異なるオブジェクトへの 2 つのポインターに対して true を返す可能性があるため、この方法が気に入らないという警告を表示します。しかし、何でも。また、コードをテストしていません。unique_ptr が含まれているときにコピーしようとするなど、特定のタスクで失敗する可能性があります。

于 2012-09-22T00:14:18.020 に答える
4

代入とコピー構築をサポートするクラスから継承するのは危険です。誤って基底クラスの変数に代入して派生クラスのインスタンスを半分に切ってしまうリスクがあるからです。これはほとんどのクラスに影響を与え、防ぐことはほぼ不可能です。そのため、クラスのユーザーがインスタンスをコピーするときはいつでも警戒する必要があります。

このため、ベースとして機能することを意図したクラスは、通常、コピーをサポートすべきではありません。コピーが必要な場合は、Derived* clone() const override代わりに次のようなものを提供する必要があります。

解決しようとしている問題は、おそらくそのままにして、そのようなポインターを操作するときにカスタムコンパレーターを提供することで対処するのが最善です。

std::vector<std::shared_ptr<int>> ii = …;
std::sort(begin(ii), end(ii),
          [](const std::shared_ptr<int>& a, const std::shared_ptr<int>& b) {
              return *a < *b;
          });
于 2012-09-22T01:35:42.140 に答える