简介: Boost C++
库使编写优秀的代码变得更加简单。我们将了解有关 Boost 头文件的详细信息,发现压缩的 pair 和不可复制类等有帮助的实用程序,并了解如何处理失败的断言。
简介
Boost C++
库拥有许多实用程序功能,可帮助您编写更优秀、更高效的代码。本文将介绍一些有帮助的实用程序,比如压缩的 pair 和类型特征,以及 Boost 中的一般功能,它们有助于快速地使某个类成为不可复制的,或者在断言失败时,允许您使用特定的函数调用。此外,本文还将分析 Boost 源文件(主要是头文件)以了解其幕后工作原理。
本文中的所有代码都使用 gcc-4.3.4 编译,并使用 Boost 库 1.45 版进行了测试。本文中探讨的所有实用程序功能都需要有头文件的支持,可以在编译时包含合适的头文件(通常为 boost/your_header.hpp 格式),同时指定包含路径(如果您使用的是 GNU Compiler Collection 或 GCC,则可以使用 –I
选项)才能实现相应的功能。
您需要了解模板的一些细节,才能理解本文中的内容。事实证明,模板局部特殊化方面的知识特别有价值。
压缩的 pair
标准模板库 (STL) 在头文件实用程序中定义 pair
。pair
是一种异构类型,包含一个具有类型 T1
的对象和另一个具有类型 T2
的对象。清单 1 展示了通常如何实现 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 }; |
现在这段代码很出色,但还不是最完美的。如果一个类没有成员,会发生什么?编译器仍然必须为空类分配空间,对吧?请参见 清单 2。
#include <iostream> using namespace std; class A { }; int main() { A _a; cout << sizeof(_a) << endl; } |
清单 2 的输出为 1
。编译器为每个 A
类型的对象分配 1 个字节。这意味着,如果一种类型为整数,另一种类型为一个空类,那么相应的 pair 的大小就会变为 4(通常为 x86 平台上的整数大小) + 1(空对象的大小) + 偏移,以使该对象与 4 字节边界对齐,于是 pair 的大小变成了 8 个字节。清单 3 证明了这一点。
#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 } |
现在不使用 pair,而是使用头文件 compressed_pair.hpp 中定义的 boost::compressed_pair
。清单 4 给出了使用 compressed_pair
而不是使用 STL pair 的代码。
#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; } |
清单 4 的输出为 1 8 4
。
压缩的 pair 对象的大小为 4 字节,即 std::pair
所占字节的一半。那么这种内存减少的秘密何在?技巧在于:不包含空类作为成员,Boost 中的 pair 结构是从这个空类中派生出来的。编译器优化了这个派生物,pair 类生成的对象仅为非空类的大小。清单 5 给出的代码在优化了空基类的编译器上证明了这一点。
#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 } |
让我们看看 compressed_pair
定义的 Boost 标头。compressed_pair
的关键组件是 compressed_pair_switch
和 compressed_pair_imp
。清单 6 给出了这些组件的声明。
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 }; |
compressed_pair_switch
和 compressed_pair_imp
是 “模板化” 的元素。Boost 只定义了这些模板的选定局部特殊化。清单 6 仅提及了 compressed_pair_imp<T1, T2, 1>
,但还存在其他特殊化。当第二个元素非空且第一个元素为空时,compressed_pair
(您很快将会看到)派生自 compressed_pair_imp<T1, T2, 2>
。
请注意,正如您所预期的那样,compressed_pair_imp
派生自空类。现在,让我们看看 compressed_pair
的定义,它派生自 compressed_pair_imp
(参见 清单 7)。
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 }; |
如果 pair 的第一个元素是空的,第二个元素非空,那么实例化的 compressed_pair
类会使用 compressed_pair_imp<T1, T2, 1>
作为基类。基类的第三个元素用于选择要使用哪种具体的模板特殊化。第三个元素的值由以下代码提供:
struct compressed_pair_switch<T1, T2, false, true, false>::value |
请注意 compressed_pair_imp<T1, T2, 1>
的定义:它仅定义将第二个元素用作成员的类。类似地,根据 compressed_pair.hpp 中的定义,compressed_pair_imp<T1, T2, 2>
仅将第一个成员当作成员。
方法 first ( )
和 second ( )
是从 compressed_pair
类委托给 compressed_pair_imp
类。compressed_pair_imp
中的定义如 清单 8 所示。
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_;} |
请注意,当第一个元素为空类时,compressed_pair_imp
返回 *this
。
您如何知道一个类是否是空的?
在确定一个类是否为空时,如果类的类型为 T
,仅需使用 boost::is_empty<T>::value
即可,它可从 boost/type_traits/is_empty.hpp 中获得,由 compressed_pair
使用。如果值等于 1
,则该类是空的,否则应该看到 。Boost 如何实现此功能? 清单 9 提供了一种基本实现。
#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; } |
清单 9 基于编译器会优化空基类的假设。现在,让我们看看一般的实用程序类别,比如 Boost 提供的 is_empty
。这些实用程序形成了下面将介绍的 Boost Type Traits 库。
回页首
了解 Boost Type Traits 库
那么,Boost Type Traits 库到底是什么呢?库名称本身就是一个不错的入口点。Type traits(类型特征) 指的是关于类型的信息。您希望确定的关于类型的一些典型信息可能包括:它为基础类型、枚举类型、联合、类还是引用类型,它是否具有没有含义(trivial)的构造函数和解构函数,等等。类型特征有 3 种基本用途:
is_pointer
、is_void
、is_array
。 is_same<T1, T2>
、is_base_and_derived<T1, T2>
。 remove_const<T>::type
还将创建与 T
相同的类型,但删除了 const
修饰符,remove_cv<T>::type
将创建与 T
相同的类型,但删除了 const
和 volatile
修饰符。 要使用 Type Traits 库,则必须将 type_traits.hpp 包含在您的代码中。清单 10 给出了类型特征所提供的一些功能。
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; |
为什么您希望使用 Type Traits 库?答案取决于这样的事实,您常常需要创建泛型库,以及对于某些具体类型,您希望避免泛型行为和拥有特殊化的实现。Type Traits 库可帮助实现这些目标。本文不会直接深入剖析针对类型特征的 Boost 标头,因为具体实现非常复杂,很难用一片文章解释清楚,但是本文将探讨典型实现战略的一些用途和理念。清单 11 提供了 is_array
特征的一种潜在实现。
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; }; |
这很简单,数据类型的特殊化有助于您实现此目标。您将在代码中使用 array<T>::value
并采取相应的措施。请注意,这与 Boost 的方式并不完全相同。类型特征模板派生自一个 true-type 或 false-type。所以在 清单 11 中,针对数组的特殊化版本将派生自 true-type,而泛型版本将派生自 false-type。我们来看一个使用实际的 Boost 标头的例子(清单 12)。
#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 } |
is_pointer<T>
; 清单 13 展示了如何实现 is_pointer<T>
。(针对指针的局部特殊化是预期的路线。)
template <typename T> struct is_pointer : public false_type{}; template <typename T> struct is_pointer<T*> : public true_type{}; |
对于 is_enum
等一些实用程序,没有实现此目标的轻松方法,必须依赖于具体的编译器来源来实现想要的结果。这使得代码与平台相关联,请参阅 Boost 文档以了解更多的详细信息。
使类不可复制,Boost 方式
如果您需要使类成为不可复制的,典型的实现方法是将类的复制构造函数和赋值运算符设置为 private 或 protected。如果二者都未定义,那么编译器会提供一种作为公共成员函数的隐式版本。Boost 提供了一种更简单的实现此目标的方法,它为您提供了一个可在头文件 noncopyable.hpp 中定义的 noncopyable
类。如果您希望使您自己的类不可复制,只需从此类中派生该类即可。派生是 public、protected 还是 private 都没有关系:您的类始终为 noncopyable
。清单 14 展示了应该如何实现派生。
#include <boost/noncopyable.hpp> #include <iostream> class A : public boost::noncopyable { public: A( ) { std::cout << “In A\n” << std::endl; } }; |
现在,尝试为类 A
使用一个复制结构和运算符赋值,在 清单 15 中声明该类。
int main() { A object1; A object2(object1); object1 = object2; return 0; } |
清单 16 显示了错误日志。
/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 |
noncopyable
类定义没有什么令人感到新奇的地方,因为 copy constructor
和 operator=
声明为 private。清单 17 给出了类声明。
class noncopyable { protected: noncopyable() {} ~noncopyable() {} private: // emphasize the following members are private noncopyable( const noncopyable& ); const noncopyable& operator=( const noncopyable& ); }; |
清单 17 中要注意的另一点是,未提供 copy constructor
和 operator=
方法的定义。如果它们已实现,从技术上讲,可以在 noncopyable
类自己的私有方法内复制该类!如果使用此实现,您会得到相应的编译时错误消息。
断言失败时的函数调用
防御性编程与让断言位于您代码中的正确位置密切相关。但如果断言失败,会发生什么?通常,您会知道断言在何处失败(文件名或行号),可能代码 会打印出一些可选的消息。在使用 Boost 时,它提供了一种不错的回调机制。如果您的表达式计算得到 false,因此触发了一个断言失败,那么将执行在头文件 assert.hpp 中声明的一个名为 assertion_failed
的预定义例程。清单 18 提供了使用 assertion_failed
的示例代码。
assertion_failed
定义断言失败时的程序行为
#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); } |
函数 assertion_failed
已在头文件 assert.hpp 中声明,但尚未定义。您必须为此函数提供一个定义。另外,必须在将 assert.hpp 头文件包含在应用程序代码之前定义宏 BOOST_ENABLE_ASSERT_HANDLER
。清单 18 的输出不言自明:在 BOOST_ASSERT
失败时调用了 assertion_failed
:
2 > 3 int main() prog.cpp 20 |
如果 BOOST_ENABLE_ASSERT_HANDLER
未定义,那么 BOOST_ASSERT
的行为与正常的 assert
相同。
交换变量的 Boost 实用程序
交换变量是在每个编程人员的生活中每天都要做的事。头文件 boost/swap.hpp 中提供的模板函数 template<class T> void swap (T& left, T& right)
允许您交换两个变量的值。那么在 STL 已提供 std::swap
时为什么还有必要使用 boost::swap
?std::swap
的行为等效于:
template <class T> void swap ( T& a, T& b ) { T c(a); a=b; b=c; } |
现在,对于存储大量数据的类,此方法可能不是交换数据的最有效方法,因为 swap
涉及到一个 copy construction
和两次赋值。另外,对于出于设计原因拥有 private 构造函数而没有复制构造函数的类,所以这种交换风格不适用。以下是 boost::swap
提供的功能:
T
类型的数组,而 std::swap
不能。 boost::swap
可调用具有签名 swap(T&, T&)
的函数,只要存在相同的签名,且不存在默认的 copy constructor
及两个赋值选项。 boost::swap
可调用 std::swap
的一个特殊化模板。 T
必须是可构造和可赋值的副本。 清单 19 给出了用于交换两个数组的 boost::swap
。
#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; } } |
boost::swap
调用您的自定义交换例程的示例如 清单 20 中所示。
#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; } |
最后,模板特殊化的版本如 清单 21 中所示。
#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; } |
现在,让我们看看实现 boost::swap
的内部原理。我们感兴趣的是如何定义 swap...for
数组。清单 22 给出了代码,它从 boost/swap.hpp 复制而来。
#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); } } |
对于数组,调用 boost::swap
最终会导致调用 void swap_impl(T (& left)[N], T (& right)[N])
,因为后者也已针对数组进行了特殊化处理。查看声明 swap_impl(T (& left)[N], T (& right)[N])
,这里 left
和 right
是具有类型 T
和大小 N
的数组的引用。两个数组必须具有相同的大小,否则您会获得编译错误消息。对于所有其他情形,会调用 swap_impl(T& left, T& right)
。查看 swap_impl(T& left, T& right)
的定义,您会看到它调用了 swap
例程。如果您拥有自己的模板特殊化的 std::swap
版本(请参见 清单 21)或全局 swap
例程(请参见 清单 20),将调用相同例程。否则,将调用 std::swap
。
最后要注意的是,swap
的声明中包含 template<typename T1, typename T2>
,但它将足够使用 T1
。这是特意这么做的,因为此声明使它没有 std::swap
那么特殊化。如果 boost::swap
和 std::swap
位于相同范围内,那么对 swap
的调用将优先于 std::swap
。
结束语
本文的内容到此就结束了。我们从使用的角度分析了一些有趣的实用程序:压缩的 pair、类型特征、不可复制类、自定义断言处理和自定义交换,还尝试着理解了它们的内部原理。严格地说,理解 Boost 头文件的内部原理不是使用这些实用程序所必需的,但知道这些原理会使各种操作变得更轻松。显然,Boost 部署的技巧对于高性能代码和库的开发是不可或缺的。