开发者

Elegant way of implementing call multiplexing in a C++ aggregate class?

When multiplexing calls to many sub-objects, what is an elegant way of preventing looping-boilerplate code?

Problem description by example:

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();
    }
  开发者_StackOverflow}
}

As you can see, the FooAggregator implements the same (similar) interface as a single Foo, iterating over all Foo objects calling their respective member functions.

As you also can see, the iteration loop is complete boilerplate, repeated for every member function of FooAggregator.

What is an elegant way of removing the boilerplate from the implementation of FooAggregators member functions


You could use Boost.Bind as @Space_C0wb0y suggested. But if you cannot use that for whatever reason, then you can do something of this sort:

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++;
        }
    }
};

Or you can use std::for_each from <algorithm> as:

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

Read about Function object to understand the second example.


In C++0x (i.e C++11), its very simple. You can use lamda in std::for_each as:

#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
};


You could use Boost.Bind to pass a boost::function object into the dispatching method that specifies which method to call. Then you would only need one dispatch-method that could be called with different target methods as parameter.


I'll take Nawaz's good 1st example and simplify some more:

(Remember, I want to reduce the boilerplate, not introduce the fanciest features.)

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


Nawaz's answer is interesting, but there are alternative solutions.

First of all, you should recognize that your aggregator is very much a Composite pattern.

Second of all, I would go either for:

  • external iteration
  • a for_each-like member method to which a functor is passed (2 actually, because of the const overload).

For external iteration, read on :)

It's relatively unfortunate that C++ iterator syntax is not really geared toward "skipping" iterators, but it is achievable nonetheless.

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

Then, your aggregate simply has Begin and End methods, and it's up to the caller to interact with your iterators.

Note: you can make it template to have mutable/const implementations in one go

External iteration remains very bulky though, because C++ lacks a generator syntax to make things simple.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜