Как перебрать контейнер в другом классе? - PullRequest
3 голосов
/ 11 ноября 2010

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

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

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

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

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

Ответы [ 3 ]

4 голосов
/ 11 ноября 2010

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

// 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);
}

Полная инкапсуляция достигнута!

4 голосов
/ 11 ноября 2010

Обычно этого достаточно:

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.

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

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

0 голосов
/ 11 ноября 2010

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

...