Using boost::bind as an improved means of calling member functions

This post takes a look at using boost::bind as a means of calling class
member functions in an efficient and generic way. It basically summarizes what has
already been said at Björn Karlsson’s excellent Informit article. Since I found the post useful, I thought it worth reproducing here, using the same status class but containing all the examples and approaches he describes in one program.

The status class

class status 
{
private:
    std::string name_;
    bool ok_;

public:
    status(const std::string& name) : name_(name),ok_(true) {}

    void break_it() 
    {
        ok_=false;
    }

    bool broken() const 
    {
        return ok_;
    }

    void print() const 
    {
        cout << name_ 
             << " is " 
             << (ok_ ? "working":"broken") 
             << '\n';
    }  
};

The STL approach

When using the the STL approach for calling member functions within a loop, one simple way to achieve this is to store the status elements within a std::vector, and iterate over them using a simple for loop, while calling their print() member functions. A shortcoming of using this approach becomes apparent: repeated calls to statuses.end(), are less efficient than useing the STL for_each algorithm:

for ( std::vector<<status>::iterator it = statuses.begin();
      it!=statuses.end();
      ++it) 
{
      it->print();
}

Having said this we can still replace the traditional for loop with the Standard Library for_each algorithm, while using an adaptor for calling the print() member function. In this example, the adaptor mem_fun_ref is used for container elements stored by value:

std::for_each( statuses.begin(),
               statuses.end(),
               std::mem_fun_ref( &status::print) );

However, there exists another potential weakness with this approach: the fact that we need to be explicit about what kind of adaptor is used depending on what the container elements are. Containers with pointers need to be specified using mem_fun instead:

std::for_each( p_statuses.begin(),
               p_statuses.end(),
               std::mem_fun( &status::print) );

What is more, should we want to store smart pointers within our container elements instead of ordinary pointers, the above code would not compile. We would not be able to do it. In this situation we would have to revert to using the same ordinary for loop approach that we wanted to avoid.

The boost::bind approach

In one fell swoop, the boost::bind approach eliminates the problems and disadvantages associated with the STL approach:
– inefficient calls to end()
– having to specify different adaptors for different container element types
– inability to store smart pointers

std::for_each( statuses.begin(),
               statuses.end(),
               boost::bind( &status::print, _1 ) );

All of the STL / Boost scenarios I have described are summarized in the code sample below.

Full code listing

#include <iostream>
#include <vector>
#include <algorithm>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>

using namespace std;  
using namespace boost;  

// Demonstration of the useage of boost::bind as explained on
// http://www.informit.com/articles/article.aspx?p=412354&seqNum=4

class status 
{
private:
    std::string name_;
    bool ok_;

public:
    status(const std::string& name) : name_(name),ok_(true) {}

    void break_it() 
    {
        ok_=false;
    }

    bool broken() const 
    {
        return ok_;
    }

    void print() const 
    {
        cout << name_ 
             << " is " 
             << (ok_ ? "working":"broken") 
             << '\n';
    }
};

int main()
{
    // Variable stored by value
    std::vector< status>  statuses;
    statuses.push_back(status( "status 1" ) );
    statuses.push_back(status( "status 2" ) );
    statuses.push_back(status( "status 3" ) );
    statuses.push_back(status( "status 4" ) );

    statuses[ 1 ].break_it();
    statuses[ 2 ].break_it();

    // Variable stored by pointer
    std::vector<status*> p_statuses;
    p_statuses.push_back( new status( "status 1" ) );
    p_statuses.push_back( new status( "status 2" ) );
    p_statuses.push_back( new status( "status 3" ) );
    p_statuses.push_back( new status( "status 4" ) );

    p_statuses[ 1 ]->break_it();
    p_statuses[ 2 ]->break_it();

    // Variable stored by smart pointer
    std::vector<boost::shared_ptr<status>> s_statuses;
    s_statuses.push_back( boost::shared_ptr<status>( new status( "status 1" ) ) );
    s_statuses.push_back( boost::shared_ptr<status>( new status( "status 2" ) ) );
    s_statuses.push_back( boost::shared_ptr<status>( new status( "status 3" ) ) );
    s_statuses.push_back( boost::shared_ptr<status>( new status( "status 4" ) ) );

    s_statuses[ 1 ]->break_it();
    s_statuses[ 2 ]->break_it();

    // Method 1: use standard for loop for
    // multiple calls to print() member function
    for ( std::vector<status>::iterator it = statuses.begin();
          it!=statuses.end();
          ++it) 
    {
          it->print();
    }

    // Method 2.1: use STL for_each loop + adaptor for calling
    // print() member function.  Vector elements 'statuses' stored by
    // value, so use mem_fun_ref adaptor.      
    std::for_each( statuses.begin(),
                   statuses.end(),
                   std::mem_fun_ref( &status::print ) );

    // Method 2.2: use STL for_each loop + adaptor for calling
    // print() member function.  Vector elements 'p_statuses' stored as
    // pointers, so use mem_fun adaptor. Disadvantage is that
    // the syntax has to change slightly.
    std::for_each( p_statuses.begin(),
                   p_statuses.end(),
                   std::mem_fun( &status::print ) );

    // Method 3.1: boost::bind, replace adaptor with boost::bind
    // _1 is substituted for arguments by function invoking 
    // the binder
    std::for_each( statuses.begin(),
                   statuses.end(),
                   boost::bind( &status::print, _1 ) );

    // Method 3.2:
    // As you can see, no changes in boost::bind usage for pointer
    // variables are necessary.
    std::for_each( p_statuses.begin(),
                   p_statuses.end(),
                   boost::bind( &status::print, _1 ) );

    // Method 3.3
    // As above but with smart pointers.  Again, boost::bind usage remains
    // exactly the same, whilst for non-boost equivalents this would not compile
    std::for_each( s_statuses.begin(),
                   s_statuses.end(),
                   boost::bind( &status::print, _1 ) );

    return 0;
}

Leave a Reply