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) { }
};