简介
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。
清单 1. 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
};
|
现在这段代码很出色,但还不是最完美的。如果一个类没有成员,会发生什么?编译器仍然必须为空类分配空间,对吧?请参见 清单 2。
清单 2. 空类的大小不为 0
#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 证明了这一点。
清单 3. pair 中使用的空类占用了更多内存
#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 的代码。
清单 4. 使用 compressed_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 给出的代码在优化了空基类的编译器上证明了这一点。
清单 5. 优化派生自空类的 pair 结构
#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 给出了这些组件的声明。
清单 6. compressed_pair 组件剖析
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)。
清单 7. compressed_pair 声明
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所示。
清单 8. 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_;}
|
请注意,当第一个元素为空类时,compressed_pair_imp 返回 *this。
您如何知道一个类是否是空的?
在确定一个类是否为空时,如果类的类型为 T,仅需使用 boost::is_empty<T>::value 即可,它可从 boost/type_traits/is_empty.hpp 中获得,由 compressed_pair 使用。如果值等于 1,则该类是空的,否则应该看到 0。Boost 如何实现此功能? 清单 9 提供了一种基本实现。
清单 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 给出了类型特征所提供的一些功能。
清单 10. Boost 提供的一些类型特征
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 特征的一种潜在实现。
清单 11. 典型的 is_array<T> 实现
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)。
清单 12. 在代码中使用 is_array<T> 和 is_pointer<T> 特征(traits)
#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>。(针对指针的局部特殊化是预期的路线。)
清单 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 展示了应该如何实现派生。
清单 14. 派生自不可复制的类
#include <boost/noncopyable.hpp>
#include <iostream>
class A : public boost::noncopyable {
public:
A( ) { std::cout << “In A\n” << std::endl; }
};
|
现在,尝试为类 A 使用一个复制结构和运算符赋值,在 清单 15 中声明该类。
清单 15. 为不可复制的对象使用复制构造函数和运算符赋值
int main()
{
A object1;
A object2(object1);
object1 = object2;
return 0;
}
|
清单 16 显示了错误日志。
清单 16. 编译清单 14 中的代码时的错误日志
/usr/include/boost/noncopyable.hpp: In copy constructor
‘<unnamed>ontTreadOnMeontTreadOnMe
(const<unnamed>ontTreadOnMe&’:
/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>ontTreadOnMe&<unnamed>::
DontTreadOnMeperator=(const<unnamed>ontTreadOnMe&’:
/usr/include/boost/noncopyable.hpp:28: error:
‘const boost::noncopyable_::noncopyable&
boost::noncopyable_::noncopyableperator=
(const boost::noncopyable_::noncopyable&’
is private
|
noncopyable 类定义没有什么令人感到新奇的地方,因为 copy constructor 和 operator= 声明为 private。清单 17 给出了类声明。
清单 17. noncopyable 类声明
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 的示例代码。
清单 18. 使用 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。
清单 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 中所示。
清单 20. 使用 boost::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;
}
|
最后,模板特殊化的版本如 清单 21 中所示。
清单 21. 使用 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;
}
|
现在,让我们看看实现 boost::swap 的内部原理。我们感兴趣的是如何定义 swap...for 数组。清单 22 给出了代码,它从 boost/swap.hpp 复制而来。
清单 22. 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, right);
}
}
}
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 部署的技巧对于高性能代码和库的开发是不可或缺的。
关于作者
Arpan Sen 是致力于电子设计自动化行业的软件开发首席工程师。他使用各种 UNIX 版本(包括 Solaris、SunOS、HP-UX 和 IRIX)以及 Linux 和 Microsoft Windows 已经多年。他热衷于各种软件性能优化技术、图论和并行计算。Arpan 获得了软件系统硕士学位。
http://www.ibm.com/developerworks/cn/aix/library/au-boostutilities/index.html