A compilation of concepts I want to remember...

Navigation
 » Home
 » About Me
 » Github

Turtlepi #5: Getting familiar with boost and its relation to ROS

11 May 2017 » ros

Having been reading a lot more ROS related code in recent days, I am starting to see a lot more use of the boost library, thus writing a quick post as a reminder to myself of the different functionality that boost offers. Will keep this an open post as I will sure to be adding new functions as I progress.

boost::bind()

Taken from the documentation the purpose of boost::bind is introduced as:

boost::bind is a generalization of the standard functions std::bind1st and std::bind2nd. It supports arbitrary function objects, >functions, function pointers, and member function pointers, and is able to bind any argument to a specific value or route input arguments into arbitrary positions. bind does not place any requirements on the function object; in particular, it does not need the result_type, >first_argument_type and second_argument_type standard typedefs.

A few examples helps to clarify and move us from abstraction to concreteness.

Consider the standard library functions std::bind1st(), and std::bind2nd() presented below. Note that we will stick with the same examples presented in the documentation.

Boost vs. Standard Library

// standard library
std::bind2nd(std::ptr_fun(f), 5)(x);    // f(x, 5)

// boost
boost::bind(f, _1, 5) (x);              // f(x, 5)

// standard library
std::bind1st(std::ptr_fun(f), 5)(x);    // f(5, x)

// boost
boost::bind(f, 5, _1)(x);               // f(5,x)

Thus in boost, _1, acts as a place holder for the first input to the function call that is not “bound”. The docs indicate that boost::bind is more flexible, and the examples given definitely makes you think that is the case.

boost::bind(f, _2, _1)(x, y);               // f(y, x)
boost::bind(g, _1, 9, _1)(x);               // g(x, 9, x)
boost::bind(g, _3, _3, _3)(x, y, z);        // g(z, z, z)
boost::bind(g, _1, _1, _1)(x, y, z);        // g(x, x, x)

Relation to ROS

Why does this matter in relation to ROS. One use case we can consider is when one wants to pass arguments to a callback.

void imageCallback(const sensor_msgs::ImageConstPtr& msg, uint32_t additional_arg)
{
    // some code
}

ros::Subscriber sub = 
    nh.subscribe<sensor_msgs::Image>("/topic", 10, boost::bind(imageCallback, _1, additional_arg));

Important

One thing to note, is that _1 indicates the target location of where the message of type sensor_msgs::Image will be directed. In this, if we observe the function definition of imageCallback(), we can see that the message is located in the first slot.

boost::shared_ptr

boost::shared_ptr is a wrapper for a raw C++ pointer which helps to manage the lifetime of the pointer. There is a preference to use a “smart” pointer in place of the typical method which leaves the responsibility of deleting the object to the programmer, which obviously raises the risk of memory leaks. Since c++ 11, the standard library has included this functionality, thus we can be using std::shared_ptr in place of boost in our own coding if necessary.

The use of smart pointers allows for the automatic deletion of objects, thus is generally considered safer and the preferred way of implementation. An example using the std::shared_ptr is presented below: [3]

void f()
{
    typedef std::shared_ptr<MyObject> MyObjectPtr; // nice short alias
    MyObjectPtr p1; // Empty

    {
        MyObjectPtr p2(new MyObject());
        // There is now one "reference" to the created object
        p1 = p2; // Copy the pointer.
        // There are now two references to the object.
    } // p2 is destroyed, leaving one reference to the object.
} // p1 is destroyed, leaving a reference count of zero. 
  // The object is deleted.

I like the idea that with a bit more code, we can guarantee safe programming, at least with respect to pointers.

Shared pointers and ROS

How does shared pointers come in to play when programming with ROS? The use of shared pointers is apparent when dealing with intraprocess publishing, i.e. when the publisher and subscriber to a particular topic exist in the same node. If we want to skip the serialize/deserialize step when considering intraprocess publishing, which is process intensive and reasons for latency, we need to publish the message as a shared pointer. See th example from the ROS Wiki: [4]

ros::Publisher pub = nh.advertise<std_msgs::String>("topic_name", 5);
std_msgs::StringPtr str(new std_msgs::String);
str->data = "hello world";
pub.publish(str);
// Note that std_msgs::StringPtr is a redefinition of boost::shared_ptr.
// From the docs [5]:
//  typedef boost::shared_ptr< ::std_msgs::String> std_msgs::StringPtr
//  

This form of publishing is what can make nodelets such a large win over nodes in separate processes. Note that when publishing in this fashion, there is an implicit contract between you and roscpp: >you may not modify the message you’ve sent after you send it, since that pointer will be passed >directly to any intraprocess subscribers. If you want to send another message, you must allocate a >new one and send that. - ROS WIKI

References

  1. http://www.boost.org/doc/libs/1_64_0/libs/bind/doc/html/bind.html
  2. http://answers.ros.org/question/12045/how-to-deliver-arguments-to-a-callback-function/
  3. http://stackoverflow.com/questions/106508/what-is-a-smart-pointer-and-when-should-i-use-one?rq=1
  4. http://wiki.ros.org/roscpp/Overview/Publishers%20and%20Subscribers
  5. http://docs.ros.org/electric/api/std_msgs/html/String_8h.html