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