C ++ 11 cast const迭代器指向shared_ptr对象的容器 [英] C++11 cast const iterator pointing to container of shared_ptr objects
问题描述
我有一个STL容器,其元素类型为 const std :: shared_ptr< MyClass>
。
I想为用户提供两个迭代器类型:
-
MyContainer :: iterator
typedefed为 std :: vector< const std :: shared_ptr< MyClass> code>
(应该是 std :: vector< const std :: shared_ptr< const MyClass>> :: const_iterator
/ p>
-
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
很多的定义自己的努力迭代器类型是由 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:
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
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 asstd::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屋!