向用户公开C ++容器迭代器

时间:2015-08-16 22:09:21

标签: c++ stl iterator

假设我有一个班级Foo,其中包含某种容器,比如说vector<Bar *> bars。我想允许用户遍历此容器,但我想要灵活,以便将来可能更改为其他容器。我已经习惯了Java,我可以做到这一点

public class Foo
{
    List<Bar> bars;         // may change to a different collection

    // User would use this
    public Iterator<Bar> getIter()
    {
        return bars.iterator();    // can change without user knowing
    } 
}

C ++迭代器的设计看起来像原始C ++指针。我如何获得等效功能?我可以执行以下操作,它将集合的开头和结尾作为迭代器返回,用户可以自己走。

class Foo
{
    vector<Bar *> bars;

public:
    // user would use this
    std::pair<vector<Bar *>::iterator , vector<Bar *>::iterator > getIter()
    {
        return std::make_pair(bars.begin(), bars.end()); 
    }
}

它有效,但我觉得我一定做错了。

  1. 函数声明暴露了我使用vector的事实。如果我改变了实现,我需要更改函数声明。这不是一个大问题,但有点违背封装。

  2. 我不需要返回可以进行自己的边界检查的类似Java的迭代器类,而是需要返回集合的.begin().end()。看起来有点难看。

  3. 我应该编写自己的迭代器类吗?

2 个答案:

答案 0 :(得分:3)

您可以调整矢量行为并提供相同的界面:

class Foo
{
    std::vector<Bar *> bars;

public:
    typedef std::vector<Bar*>::iterator iterator;

    iterator begin() {
        return bars.begin();
    }

    iterator end() {
        return bars.end();
    }
};

使用Foo::iterator作为容器外的迭代器类型。

但是,请记住,隐藏在typedef后面提供的东西比看起来要少。只要提供相同的保证,您就可以交换内部实现。例如,如果将Foo::iterator视为随机访问迭代器,则不能在以后内部交换列表的向量而不进行全面的重构,因为列表迭代器不是随机访问。

你可以参考Scott Meyers Effective STL 第2项:注意容器无关代码的错觉,以便全面论证为什么它可能是一个坏主意假设您将来可以在任何时候更改基础容器。更重要的一点是迭代器失效。假设您将迭代器视为双向的,以便您可以在某个时刻交换列表的向量。向量中间的插入将使其所有迭代器无效,而同样不适用于列表。最后,实现细节将泄露,并试图隐藏它们可能是西西弗斯的工作......

答案 1 :(得分:3)

您正在寻找类型擦除。基本上你想要一个从vector删除的迭代器。这大概是这样的:

#include <vector>
#include <memory>
#include <iostream>

template<class T>
class Iterator{ //the class that erases the iterator type
    //private stuff that the user should not care about
    struct Iterator_base{
        virtual void increment() = 0;
        virtual T &dereference() = 0;
        virtual ~Iterator_base() = default;
    };
    std::unique_ptr<Iterator_base> iter;
    template<class Iter>
    class Iterator_helper : public Iterator_base{
        void increment() override{
            ++iter;
        }
        T &dereference() override{
            return *iter;
        }
        Iter iter;
    public:
        Iterator_helper(const Iter &iter) : iter(iter){}
    };
public:
    template<class Iter>
    Iterator(const Iter &iter) : iter(new Iterator_helper<Iter>(iter)){}
    //iterator functions for the user
    Iterator &operator ++(){
        iter->increment();
        return *this;
    }
    T &operator *(){
        return iter->dereference();
    }
};

struct Bar{
    Bar(int i) : i(i){};
    int i;
};

class Foo
{
    std::vector<Bar> bars;

public:
    Foo(){ //just so we have some elements to point to
        bars.emplace_back(1);
        bars.emplace_back(2);
    }
    // user would use this
    Iterator<Bar> begin()
    {
        return bars.begin();
    }
};

int main(){
    Foo f;
    auto it = f.begin();
    std::cout << (*it).i << '\n'; //1
    ++it; //increment
    std::cout << (*it).i << '\n'; //2
    (*it).i++; //dereferencing
    std::cout << (*it).i << '\n'; //3
}

您现在可以将任何迭代器(实际上是任何东西)传递给Iterator,它支持预增量,解除引用和复制构造,完全隐藏vector内部。您甚至可以将Iterator内置vector::iterator的{​​{1}}分配给内置Iterator的{​​{1}},但这可能不是一件好事。

这是一个非常简单的实现,您还希望为后增量实现运算符list::iterator++--->,{{1 }},===<><=以及可能>=。完成后,您需要将代码复制到!=。如果您不想这样做,请考虑使用boost::type_erasure

另请注意,您使用不必要的动态内存分配,缓存未命中,可能无法内联的虚拟函数调用以及三重冗余代码([]Const_Iterator和{中的相同功能)为此封装付费{1}})。

Iterator仍然出现在Iteratr_base的{​​{1}}部分,您可以使用pimpl摆脱它,添加另一个间接级别。

我觉得这种封装不值得花费,但你的里程可能会有所不同。