Friday, April 07, 2006

C++ : In a bind with function objects part III

Given this constraint I needed to write a new member function for class A that did the work, at the time (luckily for me as it turned out) this wasn’t an option so I thought that this sounded like a job for a wrapper class!


class A_UpdateHandler
{
public:
A_UpdateHandler(Control &control, const int threshold)
: m_Control(control)
, m_Threshold(threshold)
{
}

void Update(Graphic *graphic) const
// want to pass this in as it doesn’t make
// sense for all the handler objects to
// store the same pointer
{
bool value = m_Control.GetCurrentValue() > m_Threshold;
graphic->doSomething(value);
}

private:
Control &m_Control;
const int m_Threshold;
};

typedef std::vector<A_UpdateHandler>
A_UpdateHandlerContainer;


Next I needed to create a collection of these handlers and initialise them. So a couple of (private) additions to class B did the trick.


class B
{

private:

void Init()
{
// Note: using a for-loop here to illustrate the current
// point more clearly, I’ll leave refactoring to a pref-
// erable implementation using for_each as an exercise
// for the reader! Clue: try using std::transform with
// a back_inserter.
// When I first came across this problem an Init member
// already existed in the real version of “class B”
// that iterated through the collection anyway,
// perhaps I should go back and refactor that too…

Control *control;

for(APtrContainer::iterator it = m_APCollection.begin();
it!= m_APCollection.end(); ++it)
{
A &a = **it;
std::string &name = a.GetName();
control = GetControlByName(name);
if(control)
{
const int threshold = a.GetThreshold();
m_A_UpdateHandlers.push_back(
A_UpdateHandler(*control, threshold));
}
}
}

private:
A_UpdateHandlerContainer m_A_UpdateHandlers;
};


Now class B’s update function can now be written using a call to std::for_each


void B::Update()
{
std::for_each(
m_A_UpdateHandlers.begin(),
m_A_UpdateHandlers.end(),
std::mem_fun_ref(&A_UpdateHandler::Update));
// we have a problem!
}


Another problem!! Oh no, but before we explore what it is an how I overcame it to get to the end of this refactor (finally!!) I’d like to point out the use of std::mem_fun_ref above. It does the same as std::mem_fun but works with a collection of references instead of pointers.


template<class Result, class Type>
std::mem_fun_ref_t<Result, Type>
std::mem_fun_ref(Result (Type::*pm)());


So, the problem with calling A_UpdateHandler::Update in this way is that there’s no way of passing the Graphic * parameter that we need - or is there? Enter that old favourite, std::bind2nd and the final update function looks like this:


void B::Update()
{
Graphic &graphic = GetGraphic();

std::for_each(
m_A_UpdateHandlers.begin(),
m_A_UpdateHandlers.end(),
std::bind2nd(
std::mem_fun_ref(&A_UpdateHandler::Update),
&graphic)
);
}


Which in terms of sheer aethestetics looks a lot prettier than our original for-loop! This isn’t mere triviality, in my experience elegant looking code usually indicates a more elegantly designed solution under the hood. There are several reasons why this is the case here.


... and I'll go through it with you in Part IV :)

No comments: