Mar 31, 2011

C++ Algorithms Using Member Functions

Nothing Foolish Going On (until tomorrow)


Usually a discussion about a C++ algorithm using a member function is about a member function of an object contained in a collection. For instance if you have a vector<MyClass> objects, and you want to call the method MyClass::MyMethod you find out that the key is to use the mem_fun adapter. However, I often find myself in MyOtherClass using a collection of  MyClass objects and I want to pass the MyClass object to another method of MyOtherClass. I had a hard time finding any information on this. Ergo this post.

 

Back one Step

The key to understanding how to do this started when I reviewed Chapter 3 or Stroustrup's The C++ Programming Language. It's easy to overlook chapter 3 as it's almost a coming attractions chapter for the book. When you're learning C++ for the first time, you don't understand much of the terminology. Going back and rereading it is often valuable. For instance, in this case he gives a terrific example for understanding mem_fun

Given a container of Shape objects named sc you might use the for_each algorithm like this:

for_each(sc.begin(), sc.end(), draw);

where draw is a non member function that accepts a shape pointer.  If you want to call a member function of Shape he explains that you can't do it directly but you can do it by just having draw invoke the desired method on the passed shape like this:

void draw(Shape* sp)
{
   sp->draw();
}


Great, easy to understand right? So all mem_fun does is in effect is create an anonymous function for you. Instead of actually writing a wrapper method,  just wrap the call to draw in a call to mem_fun and the compiler will do it for you. Well, you do have to specify the actual member function rather than a now non-existent method called draw. So you end up with:

for_each(sc.begin(), sc.end(), mem_fun(&Shape::Draw));

Remember, member functions take an implicit "this" parameter so you're really calling the binary function Shape::Draw(this, Shape* sp) instead of a unary one. This is important to remember when we consider using bind1st.

Now all the above is pretty clearly explained by the book. But what if we're iterating through our container of shapes in our own ShapeProcessing class? Suppose we have a
ShapeProcessing::Morph(Shape* sp);
method that we want to call. It's a member function of our current class but if we use mem_fun like above it's going to want to call Shape::Morph. The key is to force the implicit "this" parameter to be our current object not the container's object. That's where we use bind1st. We want to bind "this" – our current object to the first implicit parameter and let the 2nd parameter be the object the iterator points to. Then for_each which is looking for a unary operation will be happy.  We still have to use mem_fun because we don't want to write a non-member wrapper method   for ShapeProcessing::Morph. So here's the new call:

 bind1st(mem_fun(&ShapeProcessing::Morph),this ))

put inside the for_each it looks like this:

for_each(sc.begin(), sc.end(), bind1st(mem_fun(&ShapeProcessing::Morph),this ));

So now we're calling ShapeProcessing::Morph(this, Shape* sp) which is what we wanted. This strikes me as a little awkward but I haven't found any simpler syntax yet. The more I use it the more I'm starting to adapt to it, and the more comfortable I'm getting with it.

Let's take one more example, which is the problem I was actually trying to solve. I want to take a string and append it to an existing vector<byte>, and I want to use for_each to do it. Assuming I have a string called str and a  member variable vector<byte> m_dataVector I was hoping to do this: 

for_each(str.begin(), str.end(), bind1st(mem_func(&vector<byte>::push_back),m_dataVector ));

Unfortunately, I get a reference to a reference compiler error because push_back wants a reference to the the object to push back and bind1st wants to create a reference to its argument. Oh well. I had to fall back on a wrapper class after all

void MyClass::HowToCallPushBackInForEach(const string& str)
{
   for_each(str.begin(), str.end(), bind1st(mem_fun(&MyClass::PushBack),this ));
}


void MyClass::PushBack(const char  value)
{
    m_dataVector.push_back(value);
}

About Me

My photo
Tod Gentille (@todgentille) is now a Curriculum Director for Pluralsight. He's been programming professionally since well before you were born and was a software consultant for most of his career. He's also a father, husband, drummer, and windsurfer. He wants to be a guitar player but he just hasn't got the chops for it.