Есть ли общий способ перебора определенной переменной в группе объектов? - PullRequest
2 голосов
/ 09 августа 2009

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

class Node
{
public:
    Node* next;
    AAA dataA;
    BBB dataB;
    CCC dataC;
};

Есть ли способ сделать один итератор, который будет перебирать любую указанную переменную (вместо создания трех отдельных для каждой переменной). Я понимаю, что итератор может использовать шаблоны, чтобы перебирать типы AAA, BBB или CCC, но я не знаю, как определить, какую переменную следует возвращать.

Ответы [ 7 ]

3 голосов
/ 10 августа 2009

Лучший способ сделать это - boost bind и boost transform_iterator

Сначала вам понадобится коллекция объектов Node и итератор, который будет проходить по коллекции. Для краткости в моем примере я буду использовать std :: list.

#include <boost/bind.hpp>
#include <boost/iterator/transform_iterator.hpp>
using boost;

struct FunctionAAA
{
    void operator() (const AAA& x)
    {}
};

struct FunctionBBB
{
    void operator() (const BBB& x)
    {}
};

typedef std::list<Node> NodeList;
NodeList collection;
std::foreach (
    make_transform_iterator (collection->begin(), bind (&Node::dataA, _1)),
    make_transform_iterator (collection->end(), bind (&Node::dataA, _1)),
    FunctionAAA());

std::foreach (
    make_transform_iterator (collection->begin(), bind (&Node::dataB, _1)),
    make_transform_iterator (collection->end(), bind (&Node::dataB, _1)),
    FunctionBBB());
2 голосов
/ 09 августа 2009

Возможное решение - разделить итератор и доступ на отдельные классы:

Класс итератора, который инкапсулирует доступ к данным через аргумент шаблона:

template <typename Access>
class iterator
{
private:
  Node *current;

public:
  iterator(Node *start)
    : current(start)
  {
  }

  typename Access::typeof &operator *() const
  {
    return Access::access(*current);
  }

  bool end() const
  {
    return (current == NULL);
  }

  iterator &operator++()
  {
    if (current !=  NULL)
    {
      current = current->Next;
    }
  }

  // ... other useful operators/methods
};

Классы для доступа к различным полям данных. THose можно использовать в качестве параметров шаблона в классе итераторов:

class AccessDataA
{
public:
  typedef AAA typeof;
  static AAA &access(Node &node)
  {
    return node.dataA;
  }
};

class AccessDataB
{
public:
  typedef BBB typeof;
  static BBB &access(Node &node)
  {
    return node.dataB;
  }
};

class AccessDataC
{
public:
  typedef CCC typeof;
  static CCC &access(Node &node)
  {
    return node.dataC;
  }
};

Пример использования:

Node *start = ...;

// Loop over B:
for (iterator<AccessB> it(start); it++; !it.end())
{
  // ... *it ...
}

// Loop over C:
for (iterator<AccessC> it(start); it++; !it.end())
{
  // ... *it ...
}

Одним из улучшений будет добавление семантики, совместимой с STL, чтобы ваш список и итератор можно было использовать в методах STL, таких как std :: for_each.

1 голос
/ 10 августа 2009

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

template <typename T>
class iterator
{
private:
    Node *current;
    T Node::*var;

public:
    iterator()
        : current(NULL), var(NULL) {}

    iterator(Node *start, T Node::*var)
        : current(start), var(var)
    {
    }

    typename T &operator *() const
    {
        return current->*var;
    }

    bool end() const
    {
        return (current == NULL);
    }

    iterator &operator++()
    {
        if (current)
            current = current->next;
        return *this;
    }
};

А затем я изменил Node, чтобы иметь удобные функции для создания итераторов:

class Node
{
public:    
    Node* next;
    AAA dataA;
    BBB dataB;
    CCC dataC;

    typedef iterator<AAA> AIter;
    typedef iterator<BBB> BIter;
    typedef iterator<CCC> CIter;

    AIter getAIter()
    {
        return AIter(this, &Node::dataA);
    }

    BIter getBIter()
    {
        return BIter(this, &Node::dataB);
    }

    CIter getCIter()
    {
        return CIter(this, &Node::dataC);
    }
};

Так что теперь я могу сделать это, чтобы легко перебирать каждого члена данных моего класса:

for (Node::CIter iter = n1.getCIter(); !iter.end(); ++iter)
{
    // tada!
}
0 голосов
/ 09 августа 2009

Если я вас правильно понимаю, вы хотите перебирать данные dataA, dataB и dataC - так что это означает, что AAA, BBB и CCC все имеют один и тот же базовый тип (или, по крайней мере, имеют сходные характеристики). Почему бы просто не сохранить их в std :: vector или std :: set?

Примечание: AAA, BBB и CCC все получены из NodeType

class Node
{
public:
    Node()
    {
        dataNodes.push_back(AAA());
        dataNodes.push_back(BBB());
        dataNodes.push_back(CCC());
    }

    // AAA dataA;
    // BBB dataB;
    // CCC dataC;

    std::vector < NodeType > dataNodes;

    std::vector < NodeType >::iterator begin()
    {
        return dataNodes.begin();
    }

    std::vector < NodeType >::iterator end()
    {
        return dataNodes.end();
    }
};
0 голосов
/ 09 августа 2009

Ваш вопрос на самом деле является лишь частичным вопросом.

Вы могли бы создать адаптер итератора, который бы действовал так, как будто он перебирал коллекцию AAA, но фактически перебирал коллекцию Node s. Однако это может быть не лучшим решением вашей основной проблемы.

Я предполагаю, что у вас есть какое-то действие, которое вы хотите выполнить с каждым aaa участником. Предположим, что это был такой функтор, как этот.

struct DoAAAAction
{
    void operator()(AAA& a);
};

Вероятно, легче адаптировать действие для действия на Node.

template<class Action>
class DataA_ActionAdapter
{
public:
    DataA_ActionAdapter( Action aa ) : a( aa ) {}
    void operator()(Node& n) { a(n.dataAAA); }
private:
    Action a;
};

Это позволяет использовать стандартные алгоритмы на Node итераторах.

template<class NodeIterator, class AAAAction>
void TestAAA(NodeIterator first, NodeIterator last, AAAAction aaaa)
{
    std::for_each( first, last, DataA_ActionAdapter<AAAAction>( aaaa ) );
}
0 голосов
/ 09 августа 2009

Я сомневаюсь, что вы можете использовать шаблоны для автоматического выбора правильной переменной для возврата, за исключением указания трех специализаций шаблонов, которые будут идентичны определению трех классов. Однако вы можете создать один класс итераторов с тремя различными методами, чтобы возвращать dataA, dataB или dataC соответственно (вместо оператора * ()).

0 голосов
/ 09 августа 2009

Нет способа создать шаблон в C ++, который сам по себе будет перебирать членов типа. Для этого требуется некоторая помощь в классе в форме специализаций шаблона, специальных методов и т. Д. *

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

Не говорю, что это невозможно (может), просто это сложнее, чем простой шаблонный метод.

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