More Boost utilities

Learn how Boost works in the background

The Boost C++ libraries make it easier to write good code. Learn the features of Boost header files and discover helpful utilities like compressed pairs and non-copyable classes, as well as how to handle failed assertions.

Share:

Arpan Sen (arpansen@gmail.com), Independent author

Arpan Sen is a lead engineer working on the development of software in the electronic design automation industry. He has worked on several flavors of UNIX, including Solaris, SunOS, HP-UX, and IRIX as well as Linux and Microsoft Windows for several years. He takes a keen interest in software performance-optimization techniques, graph theory, and parallel computing. Arpan holds a post-graduate degree in software systems. You can reach him at arpansen@gmail.com.



13 September 2011

Also available in Chinese Spanish

Introduction

Boost C++ libraries have many utility features that help you write better and more effective code. This article examines some of the helpful utilities, like compressed pair and type traits as well as general features in Boost, that quickly help make a class non-copyable or allow for a specific function callback when an assertion fails. Furthermore, the article looks into the Boost sources (mostly header files) to make sense of the magic happening in the background.

All code in this article was compiled using gcc-4.3.4 and tested using Boost library version 1.45. All the utility features discussed in this article are header-only, including the appropriate headers (usually in the format boost/your_header.hpp), and specifying the include path (with the –I option if you are using GNU Compiler Collection or GCC) during compile time does the trick.

You need to understand templates in some detail to follow this article. In particular, knowledge of template partial specialization prove invaluable. If you need help with templates, please refer to the Resources section.


Compressed pair

The Standard Template Library (STL) defines pair in the header utility. pair is a heterogeneous type and holds one object of type T1 and another of type T2. Listing 1 shows how a pair would be typically implemented.

Listing 1. A typical implementation of std::pair
template<class _T1, class _T2>
struct pair
  {	// store a pair of values
       pair() : first(_T1()), second(_T2())
      { }     // construct from defaults
     
      pair(const _T1& _Val1, const _T2& _Val2)
          : first(_Val1), second(_Val2)
     { } 	// construct from specified values

      // … more stuff follows 
      _T1 first;	// the first stored value
      _T2 second;	// the second stored value
  };

Now this is all fine but not exactly optimal. What happens if, say, one of the classes has no members? The compiler still has to allocate space for the empty class, right? Look at Listing 2.

Listing 2. The size of an empty class isn't 0
#include <iostream>
using namespace std;
 
class A { };
 
int main()
{
  A _a;
  cout << sizeof(_a) << endl;
}

The output from Listing 2 is 1. The compiler allocates 1 byte for each object of type A. This means that if one of the types is an integer and the other an empty class, the size of the corresponding pair becomes 4 (typically, the size of integer on the x86 platform) + 1 (the size of an empty object) + offset to align the object on 4-byte boundaries—that is, 8 bytes. Listing 3 proves the point.

Listing 3. Empty classes used in pair take up more memory
#include <iostream>
using namespace std;
class A { };
 
int main()
{
  A _a;
  std::pair<A, int> _b;
 
  cout << sizeof(_a) << endl; // prints 1 
  cout << sizeof(_b) << endl; // prints 8 
}

Instead of a pair, now use boost::compressed_pair, defined in the header, compressed_pair.hpp. Listing 4 shows the code using compressed_pair instead of an STL pair.

Listing 4. Using compressed_pair results in a memory-efficient executable
#include <iostream>
#include <boost/compressed_pair.hpp>
using namespace std;
class A { };

int main()
{
  A _a;
  std::pair<A, int> _b;
  boost::compressed_pair<A, int> _c;

  cout << sizeof(_a) << endl;
  cout << sizeof(_b) << endl;
  cout << sizeof(_c) << endl;
}

The output from Listing 4 is 1 8 4.

The size of the compressed pair object is 4 bytes—half what std::pair takes. So what's the secret behind this memory reduction? Here's the trick: Instead of containing the empty class as a member, the pair structure in Boost derives from the empty class. The compiler optimizes such derivation, and the generated objects for the pair class are only the size of the non-empty class. Listing 5 shows the code that proves the point on compilers optimizing empty base classes.

Listing 5. Optimizing the pair structure by deriving from the empty class
#include <iostream>
using namespace std;
class A { };

struct modified_pair : public A { 
   int n;
}; 
 
int main()
{
  A _a;
  std::pair<A, int> _b;
  modified_pair _c; 
 
  cout << sizeof(_a) << endl;  // prints 1
  cout << sizeof(_b) << endl;  // prints 8 
  cout << sizeof(_c) << endl;  // prints 4 
}

Let's examine the Boost headers for the compressed_pair definition. The key components of compressed_pair are compressed_pair_switch and compressed_pair_imp. Listing 6 shows the declarations for the same.

Listing 6. Components of compressed_pair dissected
template <class T1, class T2, bool IsSame, bool FirstEmpty, bool SecondEmpty>
struct compressed_pair_switch;

template <class T1, class T2, int Version> 
class compressed_pair_imp;

// Let's consider specific partial specializations
template <class T1, class T2>
struct compressed_pair_switch<T1, T2, false, true, false>
{static const int value = 1;};

template <class T1, class T2>
struct compressed_pair_switch<T1, T2, false, false, true>
{static const int value = 2;};

template <class T1, class T2>
class compressed_pair_imp<T1, T2, 1> : protected ::boost::remove_cv<T1>::type
{
  typedef T1                                                 first_type;
  typedef T2                                                 second_type;
   // … 
   private: 
      second_type second_;  // Only the second element is a class member 
};

The compressed_pair_switch and compressed_pair_imp are "templatized" elements. Boost defines only select partial specializations of these templates. Listing 6 mentioned only compressed_pair_imp<T1, T2, 1>, but other specializations exist. When the second element is non-empty and the first element is empty, compressed_pair (as you'll soon see) is derived from compressed_pair_imp<T1, T2, 2>.

Note that, as expected, the compressed_pair_imp is derived from the empty class. Now, let's look into the definition of compressed_pair, which is derived from compressed_pair_imp (see Listing 7).

Listing 7. compressed_pair declaration
template <class T1, class T2>
class compressed_pair
   : private ::boost::details::compressed_pair_imp<T1, T2,
             ::boost::details::compressed_pair_switch<
                    T1,
                    T2,
                    ::boost::is_same<typename remove_cv<T1>::type, 
                    	typename remove_cv<T2>::type>::value,
                    ::boost::is_empty<T1>::value,
                    ::boost::is_empty<T2>::value> ::value>
{ 
// … code for the class follows
};

If the first element of the pair is empty and the second element is not, then the instantiated compressed_pair class would have compressed_pair_imp<T1, T2, 1> as the base class. The third element of the base class is used to choose which specific template specialization to use. The value for the third element is provided by:

struct compressed_pair_switch<T1, T2, false, true, false>::value

Note the definition of compressed_pair_imp<T1, T2, 1>: This only defines the class to have the second element as a member. Likewise, compressed_pair_imp<T1, T2, 2> as defined in compressed_pair.hpp has only the first element as a member.

The methods first ( ) and second ( ) are delegated from the compressed_pair class to the compressed_pair_imp class. The definitions for the same in compressed_pair_imp are shown in Listing 8.

Listing 8. The first and second methods for compressed_pair
typedef typename call_traits<first_type>::reference        first_reference;
typedef typename call_traits<second_type>::reference   second_reference;
      
typedef typename call_traits<first_type>::const_reference      first_const_reference;
typedef typename call_traits<second_type>::const_reference second_const_reference;

first_reference            first()       {return *this;}
first_const_reference first() const {return *this;}

second_reference       second()       {return second_;}
second_const_reference second() const {return second_;}

Note that when the first element is an empty class, compressed_pair_imp returns *this.


How can you tell whether a class is empty?

To determine whether a class is empty, if T is the class type, just use boost::is_empty<T>::value, available from boost/type_traits/is_empty.hpp, which is what the compressed_pair used. If value equals 1, the class is empty; otherwise, you should see 0. How does Boost implement this? Listing 9 provides a basic implementation.

Listing 9. Code to figure out whether a class is empty
#include <iostream>
using namespace std;
 
template <typename T>
struct is_empty<int>
{
    static const int value = 0;
};
 
class A { };
class B { double d; };
 
int main()
{
  cout << is_empty<A>::value << endl;
  cout << is_empty<B>::value << endl;
}

Listing 9 is based on the assumption that the compiler optimizes away the empty base classes. Now, let's look into the general category of utilities like is_empty that Boost provides. These utilities form the Boost Type Traits library described next.


Learning the Boost Type Traits Library

So, what exactly is the Boost Type Traits Library? The library name itself is a good place to start. Type traits refer to information about a type. Some of the typical information you might want to figure out about a type include whether it is a fundamental type, an enumerated type, a union, or a class or reference type; whether it has trivial constructor and destructor; and so on. Type traits serve three fundamental purposes:

  • To determine information about a type—for example, is_pointer, is_void, is_array.
  • To test the relationship between two types—for example, is_same<T1, T2>, is_base_and_derived<T1, T2>.
  • To transform one type to another—for example, remove_const<T>::type would create the same type as T but with the const qualifier removed, remove_cv<T>::type would create the same type as T but with const and volatile qualifiers removed.

To use the Type Traits Library, you must include type_traits.hpp in your application code. Listing 10 shows just some of the functionality that the type traits provide.

Listing 10. Some of the type traits that Boost provides
template <typename T>
struct is_empty;

template <typename T>
struct is_array;

template <typename T>
struct is_class;

template <typename T>
struct is_floating_point;

template <typename T>
struct is_enum;

template <typename T>
struct is_function;

Why do you want to use the Type Traits Library anyway? The answer lies in the fact that you often need to create generic libraries, and for some specific types, you want to avoid the generic behavior and have a specialized implementation. The Type Traits Library can help you get there. This article doesn't delve into the Boost headers directly for type traits—the implementation is way too involved for a single article to explain—but it discusses some uses and thoughts on typical implementation strategies. Listing 11 offers a potential implementation for the is_array trait.

Listing 11. Typical is_array<T> implementation
template<class T>
struct is_array{
  static const bool value = false;
};

template<class T, std::size_t N>
struct is_array< T (&)[N] >{
  static const bool value = true;
};

That was simple; a specialization for the array type helped you get there. You would use array<T>::value in your code and take action accordingly. Note that this is not exactly the way Boost does it. Type traits templates are derived from either a true-type or false-type. So in Listing 11, the specialized version for arrays would be derived from true-type, while the generic version would come from false-type. Consider an example using actual Boost headers (Listing 12).

Listing 12. Using is_array<T> and is_pointer<T> traits in your code
#include <iostream>
#include <boost/type_traits.hpp>

using namespace std;

int main() 
{
    cout << boost::is_array<int[10]>::value << endl;  // outputs 1
    cout << boost::is_array<int[ ]>::value << endl;  // outputs 1
    cout << boost::is_array<int*>::value << endl;  // outputs 0
    cout << boost::is_pointer<int[ ]>::value << endl;  // outputs 0
    cout << boost::is_pointer<float*>::value << endl; // outputs 1
}

That was is_pointer<T>; Listing 13 shows how is_pointer<T> is likely to be implemented. (Partial specialization for pointers is the expected route.)

Listing 13. Typical is_pointer<T> implementation
template <typename T> 
struct is_pointer : public false_type{};

template <typename T> 
struct is_pointer<T*> : public true_type{};

For some of the utilities like is_enum, there is no easy way to do this, and the implementation has to depend on specific compiler sources to achieve the desired result. That makes the code platform-specific, so please refer to the Boost documentation for more details.


Making a class non-copyable, the Boost way

If you need to make your class non-copyable, the typical way is to make the copy constructor and the assignment operator of the class private or protected. If neither of the two is defined, the compiler provides an implicit version that is a public member function. Boost provides an easier way to get this done by providing you with a noncopyable class defined in the header noncopyable.hpp. If you want to make your own class non-copyable, you just derive from this class. It doesn't matter whether the derivation is public, protected, or private: Your class is always noncopyable. Listing 14 shows how the derivation should be done.

Listing 14. Deriving from the noncopyable class
#include <boost/noncopyable.hpp>
#include <iostream>

class A : public boost::noncopyable { 
public: 
   A( ) { std::cout << “In A\n” << std::endl; }
};

Now, try using a copy construction and operator assignment for class A, declared in Listing 15.

Listing 15. Using copy constructor and operator assignment for a non-copyable object
int main()
{
    A object1;
    A object2(object1);
    object1 = object2;
    return 0;
}

Listing 16 shows the error log.

Listing 16. Error Log when compiling the code in Listing 14
/usr/include/boost/noncopyable.hpp: In copy constructor 
	‘<unnamed>::DontTreadOnMe::DontTreadOnMe
	(const<unnamed>::DontTreadOnMe&)’:
/usr/include/boost/noncopyable.hpp:27: error: 
	‘boost::noncopyable_::noncopyable::noncopyable
	(const boost::noncopyable_::noncopyable&)’ 
	is private

/usr/include/boost/noncopyable.hpp: In member function 
	‘<unnamed>::DontTreadOnMe&<unnamed>::
	DontTreadOnMe::operator=(const<unnamed>::DontTreadOnMe&)’:
/usr/include/boost/noncopyable.hpp:28: error: 
	‘const boost::noncopyable_::noncopyable& 
	boost::noncopyable_::noncopyable::operator=
	(const boost::noncopyable_::noncopyable&)’ 
	is private

The noncopyable class definition presents no surprises, as the copy constructor and operator= are declared private. Listing 17 shows the class declaration.

Listing 17. The noncopyable class declaration
class noncopyable
  {
   protected:
      noncopyable() {}
      ~noncopyable() {}
   private:  // emphasize the following members are private
      noncopyable( const noncopyable& );
      const noncopyable& operator=( const noncopyable& );
  };

The only other thing to note in Listing 17 is that the definitions for the copy constructor and operator= methods are not provided. Had they been implemented, it would have been technically possible to copy the noncopyable class within its own private methods! With this implementation, you get neat compile time error messages.


Function callback when an assertion fails

Defensive programming is all about having assertions at the right place in your code. But what happens if an assertion fails? Typically, you get to know where the assertion failed (file name or line number) and maybe some optional message the code printed. In comes Boost, which provides a nice callback mechanism. If your expression evaluates as false and thereby triggers an assertion failure, a predefined routine called assertion_failed declared in the header file assert.hpp is executed. Listing 18 provides sample code using assertion_failed.

Listing 18. Using assertion_failed to define program behavior when assertions fail
#include <iostream>
using namespace std;
 
#define BOOST_ENABLE_ASSERT_HANDLER
#include <boost/assert.hpp>
 
namespace boost { 
void assertion_failed(char const * expr, char const * function, 
                             char const * file, long line)
{
   cout << expr << endl;  // the offending expression 
   cout << function << endl;  // calling function 
   cout << file << endl;   // file which contains the assertion
   cout << line << endl;  // line number where assert failed
}
}
 
int main( )
{ 
   BOOST_ASSERT(2 > 3);
}

The function assertion_failed is declared in the header assert.hpp but not defined. You must provide a definition for this function. Also, the macro BOOST_ENABLE_ASSERT_HANDLER must be defined before including the assert.hpp header in application code. The output from Listing 18 is self-explanatory: assertion_failed is called the moment BOOST_ASSERT fails:

2 > 3
int main()
prog.cpp
20

If BOOST_ENABLE_ASSERT_HANDLER is not defined, then the behavior of BOOST_ASSERT is the same as that of normal assert.


Boost utility to swap two variables

Swapping variables is something of a daily chore in every programmer's life. The template function template<class T> void swap (T& left, T& right) available in the header boost/swap.hpp allows you to swap the values of two variables. So why is boost::swap interesting when STL already provides std::swap? The behavior of std::swap is equivalent to:

template <class T> void swap ( T& a, T& b )
{
  T c(a); 
  a=b; 
  b=c;
}

Now, for classes that store large amounts of data, this method may not be the most efficient way to swap data, because swap involves one copy construction and two assignments. Also, for classes that have design reasons to have private or no copy constructor, this style of swapping won't work. Here's what boost::swap gets you:

  • You can swap arrays of type T, std::swap can't.
  • boost::swap can invoke a function with signature swap(T&, T&) if the same exists instead of the default one copy constructor plus two assignments option.
  • boost::swap can invoke a template specialization of std::swap.
  • If neither the second nor third option above is a valid option, T must be copy constructible and assignable.

Listing 19 shows boost::swap being used to swap two arrays.

Listing 19. Using boost::swap to swap two arrays
#include <boost/swap.hpp>
#include <boost/foreach.hpp>
#include <iostream>
using namespace std;
 
int main()
{
  int a[] = {10, 20, 30, 40};
  int b[] = {4, 3, 2, 1};
 
  boost::swap(a, b); // using std::swap here won't work 
 
  BOOST_FOREACH(int t, a) { cout << t << endl; }
  BOOST_FOREACH(int t, a) { cout << t << endl; }
}

An example of boost::swap invoking your custom swap routine is shown in Listing 20.

Listing 20. Using boost::swap to implement a custom swap
#include <boost/swap.hpp>
#include <iostream>
using namespace std;
 
typedef struct T { 
  int m_data;
  T(int data) : m_data(data) { }
} T;
 
void swap(T& a, T& b) // custom swap routine that boost ::swap calls
{
  cout << "In custom swap" << endl;
  a.m_data ^= b.m_data;
  b.m_data ^= a.m_data;
  a.m_data ^= b.m_data;
}
 
int main()
{
  T a(30), b(10);
  boost::swap(a, b);
  cout << a.m_data << endl;
  cout << b.m_data << endl;
}

Finally, the template specialized version is shown in Listing 21.

Listing 21. Using a template-specialized version of std::swap
#include <boost/swap.hpp>
#include <iostream>
using namespace std;
 
typedef struct T { 
  int m_data;
  T(int data) : m_data(data) { }
} T;
 
namespace std { 
template<
void swap<T> (T& a, T& b) 
{
  cout << "In template-specialized swap" << endl;
  a.m_data ^= b.m_data;
  b.m_data ^= a.m_data;
  a.m_data ^= b.m_data;
}
}
 
int main()
{
  T a(30), b(10);
  boost::swap(a, b);
  cout << a.m_data << endl;
  cout << b.m_data << endl;
}

Now, let's look into the internals of how you implement boost::swap. Of specific interest is how the swap...for arrays are defined. Listing 22 shows the code, copied from boost/swap.hpp.

Listing 22. Source code for boost::swap
#include <algorithm> //for std::swap
#include <cstddef> //for std::size_t

namespace boost_swap_impl
{
  template<class T>
  void swap_impl(T& left, T& right)
  {
    using namespace std;//use std::swap if argument dependent lookup fails
    swap(left,right);
  }

template<class T, std::size_t N>
  void swap_impl(T (& left)[N], T (& right)[N])
  {
    for (std::size_t i = 0; i < N; ++i)
    {
      ::boost_swap_impl::swap_impl(left[i], right[i]);
    }
  }
} 

namespace boost
{
  template<class T1, class T2>
  void swap(T1& left, T2& right)
  {
    ::boost_swap_impl::swap_impl(left, right);
  }
}

For arrays, calling boost::swap ultimately results in a call to void swap_impl(T (& left)[N], T (& right)[N]), because the same is specialized for arrays. Observe the declaration swap_impl(T (& left)[N], T (& right)[N]); here, left and right are references to array of type T and size N. Both arrays must be of same size, or else you get a compilation error. For all other cases, swap_impl(T& left, T& right) is called. Looking at the definition of swap_impl(T& left, T& right), you see that it makes a call to the swap routine. If you have your template-specialized version of std::swap (refer back to Listing 21) or a global swap routine (refer back to Listing 20), the same will be called. Otherwise, std::swap is called.

Finally, note that the declaration of swap has template<typename T1, typename T2>, although it would have sufficed to use T1. This is intentional, because this declaration makes it less specialized than std::swap. If boost::swap and std::swap are available in the same scope, a call to swap would give precedence to std::swap.


Conclusion

That's it for this article. We looked into quite a few interesting utilities—compressed pairs, type traits, non-copyable classes, custom assertion handling, and custom swapping—from a usage standpoint and also tried to understand the internals whenever possible. Strictly speaking, understanding the internals of the Boost headers isn't required to use these utilities but knowing them makes the way easier on a variety of planes. Most notably, the tricks that Boost deploys make for performance-efficient code and library development.

Resources

Learn

Get products and technologies

  • Try out IBM software for free. Download a trial version, log into an online trial, work with a product in a sandbox environment, or access it through the cloud. Choose from over 100 IBM product trials.
  • Learn more about the Boost C++ libraries.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into AIX and Unix on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=AIX and UNIX
ArticleID=756312
ArticleTitle=More Boost utilities
publish-date=09132011