C ++ 11 cast const迭代器指向shared_ptr对象的容器 [英] C++11 cast const iterator pointing to container of shared_ptr objects

查看:213
本文介绍了C ++ 11 cast const迭代器指向shared_ptr对象的容器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个STL容器,其元素类型为 const std :: shared_ptr< MyClass>



I想为用户提供两个迭代器类型:


  1. MyContainer :: iterator

typedefed为 std :: vector< const std :: shared_ptr< MyClass> code>
(应该是 std :: vector< const std :: shared_ptr< const MyClass>> :: const_iterator / p>


  1. MyContainer :: const_iterator

typedefed为 std :: vector< const std :: shared_ptr< const MyClass>> :: iterator
它应该是与 std :: vector< const std :: shared_ptr< const MyClass>>> :: const_iterator

$ <

换句话说,我希望 const 引用 MyClass constness,而不是 shared_ptr constness。我发现获得第二个迭代器类型的解决方案是获得第一个,这是很容易的使用 vector :: begin ),然后使用 static_cast 将其转换为第二种类型(fixme: const_cast ,因为我添加了const,而不是删除它)。



这是常见的好设计方式

解决方案


typedefed为 std :: vector< const std :: shared_ptr< MyClass>> :: iterator (应该是 std :: vector< std :: shared_ptr< const MyClass>> :: const_iterator


但它可能不是同一类型。如果 iterator const_iterator 类型定义在向量 code>,则它们是完全不相关的类型:

 模板< typename T> 
类向量
{
class iterator;
class const_iterator;
// ...


$ b b

向量< const int> 是与向量< int> 不同的类型,因此它们的嵌套类型也不同。就编译器而言,它们是完全不相关的类型,即你不能将 const 移动到这个类型的任何点,并获得兼容的类型:

 矢量< const shared_ptr< const T>> :: iterator 

不能使用 const_cast 在不相关类型之间进行转换。可以使用 static_cast 向量< T> :: iterator 转换为向量< T> :: const_iterator 但它不是一个真正的转换,你正在构造后者从前者,这是允许的,因为这种转换是标准所需。



您可以将 shared_ptr< const T> 转换为 shared_ptr< T> c $ c> const_pointer_cast< T> 但又是因为它被定义为按标准工作,而不是因为类型是固有的兼容性,而不是因为它只是工作,如纯粹的指针。 p>

由于向量的迭代器不提供所需的深度常量,因此您需要自己写,但并不难:

  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&参考;
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; }

引用运算符*()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> ; ,因此您需要在某处缓存转换的值,以便可以返回对它的引用。 N.B.这样做稍微打破了迭代器的要求,因为下面的代码不能像预期的那样工作:

  MyContainer :: const_iterator ci = c。开始(); 
const shared_ptr< const MyClass>& ref = * ci;
const MyClass * ptr = ref.get();
++ ci;
(void)* ci;
assert(ptr == ref.get()); //失败!

断言失败的原因是 * ci 不返回对容器的底层元素的引用,而是返回迭代器的成员,该成员通过以下增量和取消引用来修改。如果这种行为是不可接受的,你需要从迭代器返回一个代理,而不是缓存一个值。或者当 const_iterator 被取消引用时,返回 shared_ptr (获得这100%的权利的困难是STL容器不试图建立深的constness的原因之一)



很多的定义自己的努力迭代器类型是由 boost :: iterator_adaptor 实用程序,所以上面的例子只是真正有用的exposition。使用该适配器,您只需要这样做,以获得您自己的具有所需行为的自定义迭代器类型:

  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 have an STL container whose element type is const std::shared_ptr<MyClass>.

I want to supply two iterator types to the user:

  1. MyContainer::iterator

typedefed as std::vector<const std::shared_ptr<MyClass>>::iterator (which should be the same type as 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 (which should be the same type as std::vector<const std::shared_ptr<const MyClass>>::const_iterator

In other words, I want the "const" to refer to the MyClass constness, not shared_ptr constness. The solution I found for getting the second iterator type is getting the first one, which is easy (e.g. using vector::begin), and then converting it to the second type using static_cast (fixme: no need to use const_cast because I'm adding constness, not removing it).

Would that be the common good-design way to achieve that, or there's a better/more common way?

解决方案

typedefed as std::vector<const std::shared_ptr<MyClass>>::iterator (which should be the same type as std::vector<std::shared_ptr<const MyClass>>::const_iterator

But it probably isn't the same type. Iterators are not just pointers. If the iterator and const_iterator types are defined inside vector then they are completely unrelated types:

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

vector<const int> is a different type to vector<int> and so their nested types are also different. As far as the compiler is concerned they are completely unrelated types, i.e. you cannot just move const around to any point in this type and get compatible types:

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

You cannot use const_cast to convert between unrelated types. You can use static_cast to convert a vector<T>::iterator to a vector<T>::const_iterator but it's not really a cast, you're constructing the latter from the former, which is allowed because that conversion is required by the standard.

You can convert a shared_ptr<const T> to a shared_ptr<T> with const_pointer_cast<T> but again only because it's defined to work by the standard, not because the types are inherently compatible and not because it "just works" like plain ol' pointers.

Since vector's iterators don't provide the deep-constness you want, you'll need to write your own, but it's not hard:

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

    // ...    
};

That iterator type is mising a few things (operator--, operator+) but they're easy to add, following the same ideas as already shown.

The key point to notice is that in order for const_iterator::operator* to return a reference, there needs to be a shared_ptr<const MyClass> object stored as a member of the iterator. That member acts as a "cache" for the shared_ptr<const MyClass> value, because the underlying container's real elements are a different type, shared_ptr<MyClass>, so you need somewhere to cache the converted value so a reference to it can be returned. N.B. Doing this slightly breaks the iterator requirements, because the following doesn't work as expected:

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!

The reason the assertion fails is that *ci doesn't return a reference to an underlying element of the container, but to a member of the iterator, which gets modified by the following increment and dereference. If this behaviour isn't acceptable you'll need to return a proxy from your iterator instead of caching a value. Or return a shared_ptr<const MyClass> when the const_iterator is dereferenced. (The difficulties of getting this 100% right is one of the reasons STL containers don't try to model deep constness!)

A lot of the effort of defining your own iterator types is done for you by the boost::iterator_adaptor utility, so the example above is only really useful for exposition. With that adaptor you'd only need to do this to get your own custom iterator types with the desired behaviour:

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

这篇关于C ++ 11 cast const迭代器指向shared_ptr对象的容器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆