2

要素タイプが。のSTLコンテナがありconst std::shared_ptr<MyClass>ます。

2つのイテレータタイプをユーザーに提供したいと思います。

  1. MyContainer::iterator

typedefed as std::vector<const std::shared_ptr<MyClass>>::iterator (これはと同じタイプである必要がありますstd::vector<const std::shared_ptr<const MyClass>>::const_iterator

  1. MyContainer::const_iterator

typedefed as std::vector<const std::shared_ptr<const MyClass>>::iterator (これはと同じタイプである必要がありますstd::vector<const std::shared_ptr<const MyClass>>::const_iterator

言い換えれば、私は「 」が恒常性ではなく恒常性constを指すようにしたいのです。2番目のイテレータタイプを取得するために私が見つけた解決策は、最初のイテレータを取得することです。これは簡単です(たとえば、を使用)。次に、 (fixme:constnessを追加しているので、削除するのではなく、使用する必要はありません)を使用して2番目のタイプに変換します。 )。MyClassshared_ptrvector::beginstatic_castconst_cast

それはそれを達成するための一般的な優れた設計方法でしょうか、それともより良い/より一般的な方法がありますか?

4

3 に答える 3

2

typedefed as std::vector<const std::shared_ptr<MyClass>>::iterator(これはと同じタイプである必要がありますstd::vector<std::shared_ptr<const MyClass>>::const_iterator

しかし、それはおそらく同じタイプではありません。イテレータは単なるポインタではありません。iteratorおよびconst_iteratorタイプが内部で定義されている場合、vectorそれらは完全に無関係なタイプです。

template<typename T>
class vector
{
    class iterator;
    class const_iterator;
    // ...

vector<const int>はとは異なるタイプvector<int>であるため、ネストされたタイプも異なります。コンパイラに関する限り、これらは完全に無関係な型です。つまりconst、この型の任意の場所に移動して互換性のある型を取得することはできません。

vector<const shared_ptr<const T>>::iterator

const_cast無関係なタイプ間の変換には使用できません。static_castをに変換するためにvector<T>::iterator使用できますがvector<T>::const_iterator、実際にはキャストではありません。前者から後者を構築しています。これは、標準で変換が必要なため許可されています。

shared_ptr<const T>aをshared_ptr<T>withに変換できますが、const_pointer_cast<T>これも標準で機能するように定義されているためであり、型が本質的に互換性があるためではなく、単純なol'ポインタのように「機能する」ためでもありません。

のイテレータは必要な深い定数を提供しないためvector、独自に作成する必要がありますが、難しくはありません。

class MyClass { };

class MyContainer
{
    typedef std::vector<std::shared_ptr<MyClass>> container_type;

    container_type m_cont;

public:

    typedef container_type::iterator iterator;

    class const_iterator
    {
        typedef container_type::const_iterator internal_iterator;
        typedef std::iterator_traits<internal_iterator> internal_traits;

        const_iterator(internal_iterator i) : m_internal(i) { }
        friend class MyContainer;

    public:

        const_iterator() { }
        const_iterator(iterator i) : m_internal(i) { }

        typedef std::shared_ptr<const MyClass> value_type;
        typedef const value_type& reference;
        typedef const value_type* pointer;
        typedef internal_traits::difference_type difference_type;
        typedef internal_traits::iterator_category iterator_category;

        const_iterator& operator++() { ++m_internal; return *this; }
        const_iterator operator++(int) { const_iterator tmp = *this; ++m_internal; return tmp; }

        reference operator*() const { m_value = *m_internal; return m_value; }
        pointer operator->() const { m_value = *m_internal; return &m_value; }

        // ...

    private:
        internal_iterator m_internal;
        mutable value_type m_value;
    };

    iterator begin() { return m_cont.begin(); }
    const_iterator begin() const { return const_iterator(m_cont.begin()); }

    // ...    
};

そのイテレータタイプはいくつかの点(operator--operator+)を見逃していますが、すでに示したのと同じアイデアに従って、簡単に追加できます。

注意すべき重要な点は、参照を返すためには、イテレータのメンバーとして格納されているオブジェクトconst_iterator::operator*が必要であるということです。基になるコンテナの実際の要素は異なるタイプであるため、shared_ptr<const MyClass>そのメンバーは値の「キャッシュ」として機能します。したがって、変換された値をキャッシュして、その値への参照を返すことができるようにする必要があります。注意:これを行うと、イテレータの要件がわずかに破られます。これは、以下が期待どおりに機能しないためです。shared_ptr<const MyClass>shared_ptr<MyClass>

MyContainer::const_iterator ci = c.begin();
const shared_ptr<const MyClass>& ref = *ci;
const MyClass* ptr = ref.get();
++ci;
(void) *ci;
assert( ptr == ref.get() );  // FAIL!

アサーションが失敗する理由は*ci、コンテナーの基になる要素への参照を返すのではなく、次の増分と逆参照によって変更されるイテレーターのメンバーへの参照を返すためです。この動作が受け入れられない場合は、値をキャッシュする代わりに、イテレータからプロキシを返す必要があります。または、が逆参照されたshared_ptr<const MyClass>ときにaを返します。const_iterator(これを100%正しくすることの難しさは、STLコンテナーが深い不変性をモデル化しようとしない理由の1つです!)

独自のイテレータタイプを定義するための多くの作業はboost::iterator_adaptor ユーティリティによって行われるため、上記の例は説明にのみ役立ちます。そのアダプタを使用すると、これを実行するだけで、目的の動作を備えた独自のカスタムイテレータタイプを取得できます。

struct iterator
: boost::iterator_adaptor<iterator, container_type::iterator>
{
    iterator() { }
    iterator(container_type::iterator i) : iterator_adaptor(i) { }
};

struct const_iterator
: boost::iterator_adaptor<const_iterator, container_type::const_iterator, std::shared_ptr<const MyClass>, boost::use_default, std::shared_ptr<const MyClass>>
{
    const_iterator() { }
    const_iterator(iterator i) : iterator_adaptor(i.base()) { }
    const_iterator(container_type::const_iterator i) : iterator_adaptor(i) { }
};
于 2013-03-01T18:25:36.457 に答える
2

boost::iterator_adaptor別のイテレータタイプに基づいて独自のイテレータタイプを定義するのは非常に簡単です。*iterしたがって、const shared_ptr<MyClass>&または必要に応じて設定できconst shared_ptr<const MyClass>&ます。

ただし、この場合、実際に持っているものがであるconst_iterator場合、間接参照はaを返すことはできません。したがって、justとして定義し、値で返します。const shared_ptr<const MyClass>&shared_ptr<MyClass>const_iterator::referenceshared_ptr<const MyClass>

#include <boost/iterator/iterator_adaptor.hpp>

class MyContainer {
public:

    class iterator;
    class const_iterator;

    class iterator :
        public boost::iterator_adaptor<
            iterator,                         // This class, for CRTP
            std::vector<const std::shared_ptr<MyClass>>::const_iterator,
                                              // Base type
            const std::shared_ptr<MyClass> >  // value_type
    {
    public:
        iterator() {}
        iterator(const iterator&) = default;

    private:
        friend class MyContainer;                 // allow private constructor
        friend class boost::iterator_core_access; // allow dereference()
        explicit iterator(base_type iter) : iterator_adaptor(iter) {}
        const std::shared_ptr<MyClass>& dereference() const
            { return *base_reference(); }
    };

    class const_iterator :
        public boost::iterator_adaptor<
            const_iterator,                        // This class, for CRTP
            std::vector<const std::shared_ptr<MyClass>>::const_iterator,
                                                   // Base type
            const std::shared_ptr<const MyClass>,  // value_type
            boost::use_default,                    // difference_type
            std::shared_ptr<const MyClass> >       // reference_type
    {
    public:
        const_iterator();
        const_iterator(const const_iterator&) = default;

        // Implicit conversion from iterator to const_iterator:
        const_iterator(const iterator& iter) : iterator_adaptor(iter.base()) {}

    private:
        friend class MyContainer;                 // allow private constructor
        friend class boost::iterator_core_access; // allow dereference()
        explicit const_iterator(base_type iter) : iterator_adaptor(iter) {}
        std::shared_ptr<const MyClass> dereference() const
            { return *base_reference(); }
    };

    iterator begin() { return iterator(mVec.begin()); }
    iterator end() { return iterator(mVec.end()); }
    const_iterator begin() const { return cbegin(); }
    const_iterator end() const { return cend(); }
    const_iterator cbegin() const { return const_iterator(mVec.begin()); }
    const_iterator cend() const { return const_iterator(mVec.end()); }

private:
    std::vector<const std::shared_ptr<MyClass>> mVec;
};
于 2013-03-01T18:31:45.823 に答える
1

shared_ptrおよびその他の標準のスマートポインターは、深い定数を念頭に置いて設計されていません。それらは、生のポインターの使用法にできるだけ近づけようとしており、生のポインターの定数は、ポインターの定数に影響を与えません。

AndreiAlexandrescuのLoki::SmartPtr(彼のModern C ++ Designで説明されています)は、ポリシーとして参照カウントと深い定数を実装します。これにより、探している効果が得られます。非標準の動作を得るために非標準のスマートポインタに切り替えてもかまわない場合は、それが1つの方法かもしれません。

于 2013-03-01T18:25:15.980 に答える