c++ 11强制转换常量迭代器,指向shared_ptr对象的容器

C++11 cast const iterator pointing to container of shared_ptr objects

本文关键字:ptr shared 对象 指向 转换 迭代器 常量 c++      更新时间:2023-10-16

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

我想为用户提供两种迭代器类型:

  1. MyContainer::iterator

typedefed为std::vector<const std::shared_ptr<MyClass>>::iterator(应与std::vector<const std::shared_ptr<const MyClass>>::const_iterator同类型

  1. MyContainer::const_iterator

类型定义为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将其转换为第二种类型(修复:不需要使用const_cast,因为我正在添加constness,而不是删除它)。

这是实现这一目标的常见的良好设计方法吗,还是有更好/更常见的方法?

类型定义为std::vector<const std::shared_ptr<MyClass>>::iterator(应该与std::vector<std::shared_ptr<const MyClass>>::const_iterator类型相同)

但它可能不是同一种类型。迭代器不仅仅是指针。如果iteratorconst_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_castvector<T>::iterator转换为vector<T>::const_iterator,但这并不是真正的强制转换,您是从前者构建后者,这是允许的,因为标准要求进行转换。

您可以将shared_ptr<const T>转换为const_pointer_cast<T>shared_ptr<T>,但再次只是因为它被定义为按标准工作,而不是因为类型本身兼容,也不是因为它"只是工作"像普通的指针。

由于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没有返回对容器底层元素的引用,而是返回对迭代器成员的引用,该成员通过下面的自加操作和解引用操作进行修改。如果这种行为是不可接受的,你需要从迭代器返回一个代理,而不是缓存一个值。或者在解引用const_iterator时返回shared_ptr<const MyClass>。(获得100%正确的困难是STL容器不尝试建立深度连续性模型的原因之一!)

定义自己的迭代器类型的很多工作都是由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) { }
};

boost::iterator_adaptor使得基于其他迭代器类型定义自己的迭代器类型非常容易。因此,您可以根据需要将*iter设置为const shared_ptr<MyClass>&const shared_ptr<const MyClass>&

虽然在const_iterator的情况下,解引用不能返回const shared_ptr<const MyClass>&,如果你实际上有shared_ptr<MyClass>。因此,我们将const_iterator::reference定义为shared_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;
};

shared_ptr和其他标准智能指针在设计时并没有考虑到深度constness。它们试图尽可能接近原始指针的用法,并且原始指针的const性不会影响指针的const性。

Andrei Alexandrescu的Loki::SmartPtr(在他的Modern c++ Design中描述)将引用计数和深度稳定性作为策略实现,这将为您提供您正在寻找的效果。如果您不介意为了获得非标准的行为而切换到非标准的智能指针,这可能是一种方法。