Элегантный способ реализации мультиплексирования вызовов в агрегатном классе C ++? - PullRequest
6 голосов
/ 20 мая 2011

При мультиплексировании вызовов ко многим подобъектам, что является элегантным способом предотвращения зацикливания кода-шаблона?

Описание проблемы на примере:

struct Foo {
  void Boo();
  void Hoo();
  bool IsActivated();
};

struct FooAggregator {
  ...
  void Boo();
  void Hoo();
  ...
  std::vector<Foo> m_foos;
};

FooAggregator::Boo() {
  for(size_t i=0, e=m_foos.size(); i!=e; ++i) {
    if(m_foos[i].IsActivated()) {
      m_foos[i].Boo();
    }
  }
}

FooAggregator::Hoo() {
  for(size_t i=0, e=m_foos.size(); i!=e; ++i) {
    if(m_foos[i].IsActivated()) {
      m_foos[i].Hoo();
    }
  }
}

Как видите,FooAggregator реализует тот же (аналогичный) интерфейс, что и отдельный Foo, итерируя по всем объектам Foo, вызывая их соответствующие функции-члены.

Как вы также можете видеть, цикл итерации является полным шаблоном, повторяющимся для каждой функции-члена FooAggregator.

Какой элегантный способ удалить шаблон из реализации функций-членов FooAggregators

Ответы [ 4 ]

6 голосов
/ 20 мая 2011

Вы можете использовать Boost.Bind, как предложено @ Space_C0wb0y. Но если вы не можете использовать это по какой-либо причине, тогда вы можете сделать что-то вроде этого:

struct FooAggregator 
{
    typedef void (Foo::*Fun)();


    void Boo() {  CallForEach(m_foos.begin(), m_foos.end(), &Foo::Boo); }
    void Hoo() {  CallForEach(m_foos.begin(), m_foos.end(), &Foo::Hoo); }

    template<typename FwdIterator>
    void CallForEach(FwdIterator first, FwdIterator last, Fun fun)
    {
        while (first != last ) 
        { 
            if(first->IsActivated())
            {
                 (first->*fun)();
            }
            first++;
        }
    }
};

Или вы можете использовать std::for_each из <algorithm> как:

#include <algorithm>

struct FooAggregator 
{
    typedef void (Foo::*Fun)();

    void Boo() {  std::for_each(m_foos.begin(), m_foos.end(), Call(&Foo::Boo)); }
    void Hoo() {  std::for_each(m_foos.begin(), m_foos.end(), Call(&Foo::Hoo)); }

    struct Call
    {
        Fun m_fun;
        Call(Fun fun) : m_fun(fun) {}
        void operator()(Foo & foo)
        {
            if(foo.IsActivated())
            {
               (foo.*m_fun)();
            }
        }
   };    
};

Прочитайте о Функциональный объект , чтобы понять второй пример.


В C ++ 0x (т.е. C ++ 11) это очень просто. Вы можете использовать lamda в std::for_each как:

#include <algorithm>

struct FooAggregator 
{
    void Boo()
    {  
         std::for_each(m_foos.begin(), m_foos.end(), [](Foo &foo){ if (foo.IsActivated()) foo.Boo(); } ); 
    }

    void Hoo()
    {  
         std::for_each(m_foos.begin(), m_foos.end(), [](Foo &foo){ if (foo.IsActivated()) foo.Hoo(); } ); 
    }
    //other code
};
1 голос
/ 21 мая 2011

Я возьму хороший первый пример Наваза и упросту еще немного:

(Помните, я хочу уменьшить шаблон, а не вводить самые модные функции.)

// FooAggregator.h
struct FooAggregator {
    template<typename MemFn>
    void CallForEachFoo(MemFn fun);

    void Boo();
    void Hoo();
};

// FooAggregator.cpp
template<typename MemFn>
void FooAggregator::CallForEachFoo(MemFn fun) {
    BOOST_FOREACH(Foo& o, m_foos) {
      if(o.IsActivated()) {
        (o.*fun)();
      }
    }
}

void Boo() {  CallForEachFoo(&Foo::Boo); }
void Hoo() {  CallForEachFoo(&Foo::Hoo); }
1 голос
/ 20 мая 2011

Вы можете использовать Boost.Bind для передачи объекта boost::function в метод диспетчеризации, который указывает, какой метод вызывать.Тогда вам потребуется только один метод отправки, который можно вызывать с разными целевыми методами в качестве параметра.

0 голосов
/ 20 мая 2011

Ответ Наваза интересен, но есть альтернативные решения.

Прежде всего, вы должны признать, что ваш агрегатор очень похож на Composite модель.

Во-вторых, ябудет использоваться либо для:

  • внешней итерации
  • for_each -подобного метода-члена, в который передается функтор (фактически 2, из-за перегрузки const).

Для внешней итерации читайте дальше:)

Относительно прискорбно, что синтаксис итератора C ++ на самом деле не ориентирован на «пропуск» итераторов, но, тем не менее, достижим.

class ActiveIterator {
public:
  friend class FooAggregator;

  friend bool operator==(ActiveIterator lhs, ActiveIterator rhs) {
    return lhs._it == rhs._it;
  }

  ActiveIterator& operator++() {
    this->next();
    return *this;
  }

  Foo* operator->() const { return _it::operator->(); }
  Foo& operator*() const { return *_it; }

private:
  typedef std::vector<Foo>::iterator base;
  ActivateIterator(base begin, base end): _it(begin), _end(end) {
    if (_it == _end || _it->IsActive()) { return; }
    this->next();
  }

  void next() {
    ++it; while (_it != _end && !_it->IsActive()) { ++_it; }
  }

  base _it, _end;
};

Тогда ваш агрегат просто имеет методы Begin и End, и вызывающий может взаимодействовать с вашими итераторами.

Примечание: вы можете сделать его шаблоном, чтобы иметь изменчивость/ const реализации за один раз

Внешняя итерация остается очень громоздкой, хотя в C ++ отсутствует синтаксис генератора, чтобы упростить задачу.

...