2010-11-11 3 views
3

Я сталкивался с этой проблемой несколько раз и хотел бы узнать, есть ли простой способ или шаблон, который я могу использовать для его решения.Как проходить через контейнер в другом классе?

Представьте себе древовидную структуру, в которой каждый узел содержит контейнер STL (вектор) указателей на детей. Я хочу, чтобы клиентский код мог пересекать это дерево и перебирать дочерние контейнеры для доступа к другим частям.

Моя проблема заключается в том, что я хочу поддерживать инкапсуляцию для своих узлов, в то же время позволяя клиентам легко видеть всех дочерних элементов для этого узла. Я также хочу убедиться, что если клиент получает ссылку на const для корневого узла в дереве, то доступ к последующим частям дерева также является константой.

Должен ли я попытаться сделать класс итератора для моего типа узла, иметь метод итераторов вектора вершин узла, или есть более элегантный шаблон, который мне не хватает?

Edit: Я хочу еще раз подчеркнуть, что в то время как я вижу некоторые хорошие идеи, представленные, у меня есть контейнеры указателей к другим узлам. Возврат vector<node *>::const_iterator не будет препятствовать вызовам клиентов неконстантными методами на узле. Он только защищает самих указателей от указания на разные объекты.

ответ

4

Что-то вроде этого, как правило, достаточно:

class node 
{ 
public: 
    typedef std::vector<node> node_list; 
    typedef node_list::const_iterator const_iterator; 

    const_iterator begin() const { return children_.begin(); } 
    const_iterator end() const { return children_.end(); } 

private: 
    node_list children_; 
} 

Это позволяет изменить базовый тип контейнера без изменения кода, который перебирает через node «s детей.

Это имеет тот недостаток, что его утечку детали реализации, потому что код, который использует ваш node::const_iterator знает, что это итератор произвольного доступа (поскольку std::vector:: const_iterator является случайным итератором доступа), так что вы можете иметь трудное время переключение на контейнер которые не поддерживают произвольный доступ.

Если вы хотите иметь возможность управлять классом итератора самостоятельно, вы, вероятно, захотите создать свой собственный класс iterator, который обеспечивает точное поведение, которое вы хотите предоставить.

+0

Будет ли это работать, если node_list - это вектор указателей на узлы, и я хочу, чтобы клиент имел только указатели на узлы константы? –

+0

«код знает, что это итератор с произвольным доступом», интересный момент, но в то же время код также знает, что это именно «вектор :: const_iterator». Тем не менее, гораздо проще использовать 'operator +', не задумываясь, чем присваивать итератору 'vector :: const_iterator' вместо' node :: const_iterator', не задумываясь. Интересно, есть ли случай для адаптера итератора, который ничего не делает, кроме «понизить» итератор, который он переносит в определенную категорию. Затем вы можете вернуть 'make_forward_iterator (children_.begin())' или некоторые из них. –

+0

@Steve: Поскольку категория является частью типа итератора, вам нужно будет сделать преобразование в typedef. Я думаю, вы могли бы сделать это довольно легко, но используя класс 'template forward_iterator_wrapper: private RealIteratorT {};'. Там могут быть проблемы; Я не думал об этом подробно. –

0

Для перемещения по всему дереву вам необходимо создать свой собственный класс итератора, чтобы просто перемещаться по ним, вы можете безопасно возвращать векторные итераторы.

4

Это не прямой ответ на ваш вопрос, а альтернативный подход. Вместо того, чтобы клиентский код запускал показ, вы можете пойти за control inversion, зарегистрировав функтор обратного вызова. Например:

// Derive from this class to create a visitor 
class AbstractVisitor 
{ 
public: 
    virtual void operator() (const T &) = 0; 
}; 


// Your recursive data-structure class 
class MyClass 
{ 
public: 
    void walk(AbstractVisitor &v) const 
    { 
     // Call the client callback 
     v(payload); 
     for (std::vector<MyClass>::const_iterator it = children.begin(); 
      it != children.end(); ++it) 
     { 
      // Recurse 
      it->walk(v); 
     } 
    } 

private: 
    T payload; // Some sort of payload associated with the class 
    std::vector<MyClass> children; 
}; 


// You could have different visitor classes to do different things 
class MyVisitor : public AbstractVisitor 
{ 
public: 
    virtual void operator() (const T &t) 
    { 
     // Do something with t 
    } 
} 


int main() 
{ 
    MyClass m; 
    MyVisitor v; 
    ... 
    m.walk(v); 
} 

Завершение полного обкатки!

+0

Шаблон посетителя! : -O (Ну, что-то связанное с этим, по крайней мере.) –

+2

Лично в C++ я бы хотел, чтобы 'walk' был шаблоном функции с параметром functor. В противном случае (потому что иногда вам не нужны шаблоны на границах интерфейса), я бы хотел, чтобы указатель пользовательских данных шел с моим указателем на функцию или с интерфейсом с виртуальной функцией. В противном случае прогулка ограничивается операциями, которые не имеют состояния, кроме глобального состояния, и не возвращают значение (потому что оно пусто). То есть, плохо разработанные ;-) –

+0

@Steve: да, абсолютно; в идеале вы хотели бы иметь возможность предоставлять его функторам. Но это было немного сложно скомбинировать для фрагмента кода выше! –

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