2014-04-29 4 views
3

Java позволяет классов выявить Iterable типы, так что клиенты могут пройти сбор некоторых экземпляра данных, например, так:Является ли Iterable-подобное поведение в C++ достижимым?

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 имеет одну коллекцию для повторения. Что еще я могу сделать?

+0

C++ имеет итераторы, хотя они работают несколько иначе, чем в Java. На самом деле интерфейс 'Iterable ' - это просто слой, который позволяет компилятору Java использовать синтаксический сахар для автоматизации фактической итерации. –

+0

@JohnGaughan Я никогда не говорил, что у C++ не было итераторов - см. Мое решение. – VF1

+0

Если 'allTs()' возвращает 'const vector &', вы можете использовать его для инициализации переменной 'const auto &' и быть изолированной от ее типа. – Oktalist

ответ

8

Скажите, не спрашивайте

Если вы часто подвергая между nal-представление вашего класса, позволяя вызывающему итерации через элементы, где инкапсуляция? Коллекционные классы должны раскрывать свои элементы - это их цель.

В C++ было бы предпочтительнее и более эффективно предоставлять механизм visitor pattern или другой double-dispatch таким образом, чтобы каждый элемент внутренней коллекции мог быть передан последовательно в Функтор. Таким образом, содержащий/посещаемый класс может сделать некоторые гарантии того, что коллекция не изменится при повторении, что может быть невозможно, если вы передадите коллекцию с помощью итератора.

Пример:

#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()); 
+0

Интересный подход! Спасибо, это хорошее применение шаблона посетителя, который предлагает элегантное решение в своем разном дизайне. – VF1

+0

Хотелось бы отметить, однако, что вы делаете некоторые предположения в своем утверждении «если вы часто публикуете внутреннее представление вашего класса, позволяя вызывающему пользователю перебирать элементы, где инкапсуляция?» И ваши, и подходы Брайана Чена обеспечивают равную инкапсуляцию (предполагая итераторы вперед). – VF1

+0

Еще одна вещь, о которой стоит упомянуть, заключается в том, что использование шаблона посетителя гораздо безопаснее - не нужно беспокоиться о согласованности старого экземпляра 'iterable' или срока службы структуры данных, которая его вернула. – VF1

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 (и много избыточности кода).

15

просто сделать Iterable в C++

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

+0

Почему и 'T', и' U'? Разве они не должны быть одинаковыми? –

+0

@ C.R. Согласен. Брайан Чен, есть причина, по которой шаблоны отличаются? – VF1

+0

Это отличная утилита. Тем не менее, я думаю, что ответ @ JBRWilkinson немного чист с точки зрения дизайна и требует меньше кода. – VF1

Смежные вопросы