可以在C ++中实现类似于Iterable的行为吗?

时间:2014-04-29 00:33:18

标签: c++

Java允许类显示Iterable类型,以便客户端可以遍历某个实例的数据集合,如下所示:

public class MyClass
{
    private ArrayList<String> strings;
    private ArrayList<Integers> ints;
    public MyClass() { /* generate data ... */ }
    public Iterable<String> allStrings() {return strings;}
    public Iterable<Integer> allInts() {return ints;}
}

这总是让我感到“干净”,因为它保持了封装,允许我根据需要将ArrayList更改为LinkedList,并且在构造中仍然方便客户端,例如for(String s : myClassInstance.allStrings()) //...

但是,在C ++中,如果我想允许客户端使用我的for-loop,在没有Iterable的情况下,我需要返回const vector<T>&或其他什么,这显然是不太好。

定义template<> begin<my_class> {/*...*/}和朋友很好,但仅当my_class有一个集合要迭代时。我还能做什么?

3 个答案:

答案 0 :(得分:21)

在C ++中创建Iterable

template<class T, class U>
struct Iterable
{
    T _begin;
    U _end;

    Iterable(T begin, U end)
    : _begin(begin), _end(end)
    {}

    T begin()
    {
        return _begin;
    }

    U end()
    {
        return _end;
    }
};

template<class T, class U>
Iterable<T,U> make_iterable(T t, U u)
{
    return Iterable<T,U>(t, u);
}

struct MyClass 
{
    std::vector<int> _ints;
    std::vector<std::string> _strings;

    auto allInts() -> decltype(make_iterable(_ints.begin(), _ints.end()))
    {
        return make_iterable(_ints.begin(), _ints.end());
    }

    auto allStrings() -> decltype(make_iterable(_strings.begin(), _strings.end()))
    {
        return make_iterable(_strings.begin(), _strings.end());
    }
};

然后像

一样使用它
for (auto i : foo.allInts())
{
    cout << i << endl;
}

for (auto i : foo.allStrings())
{
    cout << i << endl;
}

live example

答案 1 :(得分:10)

告诉,不要问

如果您经常通过允许调用者遍历元素来暴露类的内部表示,那么封装在哪里?集合类需要公开它们的元素 - 这就是它们的目的。

在C ++中,提供visitor pattern或其他double-dispatch机制会更好,更高效,这样内部集合中的每个元素都可以按顺序传递给Functor。通过这种方式,包含/可访问类可以保证集合在迭代时不会改变,如果通过迭代器分发集合,这可能是不可能的。

示例:

#include <algorithm>

template<typename T>
struct ElementPrinter
{
  void operator()(const T& elem) {
    std::cout << elem << std::endl;
  }
};

class MyClass 
{
  public:
    // (1) if you only want to print the elements
    std::ostream& print(std::ostream& s) const {
       for (std::vector<int>::const_iterator it = myInts.begin(), 
            end=myInts.end(); it != end; ++it) {
         s << *it << endl;
       }
       return s;
    }

    // (2) if you want a generic way to visit the ints
    template<typename Functor>
    void visitInts(Functor f) {
      std::for_each(myInts.begin(), myInts.end(), f);
    }
};

MyClass m;

// (1)
m.print(std::cout);

// (2)
m.visitInts(ElementPrinter());

答案 2 :(得分:0)

我有一个可能的解决方案(虽然我希望还有其他解决方案)是放弃漂亮的增强型for循环并定义几个begin/end成员函数,即my_class::begin_strings(), my_class::end_strings(), my_class::end_ints(), my_class::begin_ints()

希望还有其他选择,因为现在客户必须使用for(auto it = mci.begin_strings(); it != mci.end_strings(); ++it)。还不错,但仍然没有Java那么漂亮(以及大量的代码冗余)。