2

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

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

  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”指的是MyClass常数,而不是shared_ptr常数。我找到的获取第二个迭代器类型的解决方案是获取第一个,这很容易(例如使用vector::begin),然后使用static_cast(fixme: 不需要使用const_cast,因为我正在添加 constness,而不是删除它)将其转换为第二种类型)。

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

4

3 回答 3

2

typedefed as std::vector<const std::shared_ptr<MyClass>>::iterator(应该与std::vector<std::shared_ptr<const MyClass>>::const_iterator

但它可能不是同一类型。迭代器不仅仅是指针。如果iteratorandconst_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将 a 转换vector<T>::iterator为 avector<T>::const_iterator但它不是真正的演员,您正在从前者构造后者,这是允许的,因为标准要求进行转换。

您可以将 a 转换shared_ptr<const T>为 a shared_ptr<T>with const_pointer_cast<T>but 只是因为它被定义为按标准工作,而不是因为这些类型本质上是兼容的,也不是因为它像普通的 ol' 指针一样“工作”。

由于vector's 的迭代器不能提供你想要的深度常量,你需要自己编写,但这并不难:

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>因此您需要在某个地方缓存转换后的值,以便可以返回对它的引用。NB 这样做会稍微破坏迭代器的要求,因为以下内容无法按预期工作:

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 容器不尝试对深度常量建模的原因之一!)

定义您自己的迭代器类型的许多工作都是由该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情况下,取消引用不能返回 aconst shared_ptr<const MyClass>&如果您实际拥有的是shared_ptr<MyClass>. 所以我们将定义const_iterator::reference为justshared_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 和其他标准智能指针在设计时并未考虑到深度常量。他们试图尽可能接近原始指针的使用,并且原始指针的 const-ness 不会影响指针的 const-ness。

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

于 2013-03-01T18:25:15.980 回答