【问题标题】:How can I expose iterators without exposing the container used?如何在不暴露使用的容器的情况下暴露迭代器?
【发布时间】:2010-09-14 11:46:21
【问题描述】:

我使用 C# 已经有一段时间了,回到 C++ 是一件很头疼的事情。我正在尝试将我的一些实践从 C# 带到 C++,但我发现了一些阻力,我很乐意接受你的帮助。

我想为这样的类公开一个迭代器:

template <class T>
class MyContainer
{
public:
    // Here is the problem:
    // typedef for MyIterator without exposing std::vector publicly?

    MyIterator Begin() { return mHiddenContainerImpl.begin(); }
    MyIterator End() { return mHiddenContainerImpl.end(); }

private:
    std::vector<T> mHiddenContainerImpl;
};

我是否正在尝试一些不成问题的事情?我应该只是 typedef std::vector::iterator 吗?我希望只依赖于迭代器,而不是实现容器......

【问题讨论】:

标签: c++ stl iterator encapsulation


【解决方案1】:

您可能会发现以下文章很有趣,因为它完全解决了您发布的问题:On the Tension Between Object-Oriented and Generic Programming in C++ and What Type Erasure Can Do About It

【讨论】:

    【解决方案2】:

    我之前做过以下事情,所以我得到了一个独立于容器的迭代器。这可能有点矫枉过正,因为我也可以使用 API,其中调用者传入 vector&lt;T*&gt;&amp;,应该填充所有元素,然后调用者可以直接从向量中迭代。

    template <class T>
    class IterImpl
    {
    public:
        virtual T* next() = 0;
    };
    
    template <class T>
    class Iter
    {
    public:
        Iter( IterImpl<T>* pImpl ):mpImpl(pImpl) {};
        Iter( Iter<T>& rIter ):mpImpl(pImpl) 
        {
            rIter.mpImpl = 0; // take ownership
        }
        ~Iter() {
            delete mpImpl; // does nothing if it is 0
        }
        T* next() {
        return mpImpl->next(); 
        }
    private:
        IterImpl<T>* mpImpl; 
    };
    
    template <class C, class T>
    class IterImplStl : public IterImpl<T>
    {
    public:
        IterImplStl( C& rC )
        :mrC( rC ),
        curr( rC.begin() )
        {}
        virtual T* next()
        {
        if ( curr == mrC.end() ) return 0;
        typename T* pResult = &*curr;
        ++curr;
        return pResult;
        }
    private:
        C& mrC;
        typename C::iterator curr;
    };
    
    
    class Widget;
    
    // in the base clase we do not need to include widget
    class TestBase
    {
    public:
        virtual Iter<Widget> getIter() = 0;
    };
    
    
    #include <vector>
    
    class Widget
    {
    public:
        int px;
        int py;
    };
    
    class Test : public TestBase
    {
    public:
        typedef std::vector<Widget> WidgetVec;
    
        virtual Iter<Widget> getIter() {
            return Iter<Widget>( new IterImplStl<WidgetVec, Widget>( mVec ) ); 
            }
    
        void add( int px, int py )
        {
            mVec.push_back( Widget() );
            mVec.back().px = px;
            mVec.back().py = py;
        }
    private:
        WidgetVec mVec;
    };
    
    
    void testFn()
    {
        Test t;
        t.add( 3, 4 );
        t.add( 2, 5 );
    
        TestBase* tB = &t;
        Iter<Widget> iter = tB->getIter();
        Widget* pW;
        while ( pW = iter.next() )
        {
            std::cout << "px: " << pW->px << " py: " << pW->py << std::endl;
        }
    }
    

    【讨论】:

    • 可以,但不是标准迭代器。我宁愿不重新发明轮子。预计其他人已经习惯了标准迭代器,那里没有学习曲线。
    【解决方案3】:

    这应该做你想做的:

    typedef typename std::vector<T>::iterator MyIterator;
    

    来自Accelerated C++

    当你有一个类型,比如vector&lt;T&gt;,它依赖于一个模板参数,并且你想使用该类型的成员,比如size_type,它本身就是一个类型,你必须在整个name by typename 让实现知道将名称视为一种类型。

    【讨论】:

    • 投反对票,因为这仍然有效地公开了底层容器类型。
    【解决方案4】:

    我不确定您所说的“不公开 std::vector”是什么意思,但实际上,您可以像这样定义您的 typedef:

    typedef typename std::vector<T>::iterator iterator;
    typedef typename std::vector<T>::const_iterator const_iterator; // To work with constant references
    

    您可以稍后更改这些类型定义,而用户不会注意到任何事情...

    顺便说一句,如果您希望您的类充当容器,那么公开一些其他类型被认为是一种很好的做法:

    typedef typename std::vector<T>::size_type size_type;
    typedef typename std::vector<T>::difference_type difference_type;
    typedef typename std::vector<T>::pointer pointer;
    typedef typename std::vector<T>::reference reference;
    

    如果你的班级需要:

     typedef typename std::vector<T>::const_pointer const_pointer;
     typedef typename std::vector<T>::const_reference const_reference;
    

    你会在这里找到所有这些 typedef 的含义:STL documentation on vectors

    编辑:按照 cmets 中的建议添加了 typename

    【讨论】:

    • 也许我有点矫枉过正,但我​​想让 typedef 暴露我正在使用 stl 迭代器,而不是实际的容器。如果我做 typedef std::vector::iterator iterator,那么人们可以只做 std::vector::iterator iter = example.Begin();。 (续)
    • 虽然起初这似乎不是问题,但想象一下,如果我将类的内部实现更改为使用列表。客户端代码会中断。使用适用于许多不同容器的通用迭代器可以解决这个问题。问题是我还没有找到办法。
    • 对您的帖子稍作更正:由于 T 是模板参数,因此您必须在 typedef 中使用 typename 关键字,即 typedef typename std::vector::iterator iterator;
    • 声明,您完全符合我的担忧。如果您要声明一个纯抽象类并希望允许实现选择它们的最佳表示,那么将向量强制放入图片中无济于事。 typedef 也不能解决问题。
    猜你喜欢
    • 2018-06-19
    • 2019-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多