Professional Documents
Culture Documents
13 Sep 2011 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.
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
More Boost utilities Copyright IBM Corporation 2011 Trademarks Page 1 of 16
developerWorks
ibm.com/developerWorks
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 boundariesthat 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;
Trademarks Page 2 of 16
ibm.com/developerWorks
developerWorks
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 byteshalf 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; cout << sizeof(_b) << endl; cout << sizeof(_c) << endl; } // prints 1 // prints 8 // prints 4
Trademarks Page 3 of 16
developerWorks
ibm.com/developerWorks
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>
Trademarks Page 4 of 16
ibm.com/developerWorks
developerWorks
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 typedef typename call_traits<second_type>::reference first_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.
Trademarks Page 5 of 16
developerWorks
ibm.com/developerWorks
#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.
Trademarks Page 6 of 16
ibm.com/developerWorks
developerWorks
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 traitsthe implementation is way too involved for a single article to explainbut 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
Trademarks Page 7 of 16
developerWorks
ibm.com/developerWorks
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.
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
Trademarks Page 8 of 16
ibm.com/developerWorks
developerWorks
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.
developerWorks
ibm.com/developerWorks
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.
Trademarks Page 10 of 16
ibm.com/developerWorks
developerWorks
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
Trademarks Page 11 of 16
developerWorks
ibm.com/developerWorks
#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.
Trademarks Page 12 of 16
ibm.com/developerWorks
developerWorks
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 utilitiescompressed pairs, type traits, non-copyable classes, custom assertion handling, and custom
Trademarks Page 13 of 16
developerWorks
ibm.com/developerWorks
swappingfrom 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.
Trademarks Page 14 of 16
ibm.com/developerWorks
developerWorks
Resources
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
Trademarks Page 15 of 16
developerWorks
ibm.com/developerWorks
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.
Trademarks Page 16 of 16