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.
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>::typewould create the same type asTbut with theconstqualifier removed,remove_cv<T>::typewould create the same type asTbut withconstandvolatilequalifiers 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::swapcan't. boost::swapcan invoke a function with signatureswap(T&, T&)if the same exists instead of the default onecopy constructorplus two assignments option.boost::swapcan invoke a template specialization ofstd::swap.- If neither the second nor third option above is a valid option,
Tmust 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.
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.
Learn
-
Read C++ Templates: The Complete
Guide by David Vandevoorde and Nicolai M. Josuttis (Addison-Wesley
Professional, 2002).
-
AIX and UNIX developerWorks
zone: The AIX and UNIX zone provides a wealth of information relating to
all aspects of AIX systems administration and expanding your UNIX skills.
-
New to AIX and UNIX?
Visit the New to AIX and UNIX page to learn more.
-
Technology
bookstore: Browse the technology bookstore for books on this and other
technical topics.
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
- Follow developerWorks on Twitter.
-
Participate in developerWorks blogs and get involved in the developerWorks community.
- Get involved in the My developerWorks community.
-
Participate in the AIX and UNIX® forums:
- AIX Forum
- AIX Forum for developers
- Cluster Systems Management
- Performance Tools Forum
- Virtualization Forum
- More AIX and UNIX Forums
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.




