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 theconst
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.
精彩评论