Boost学习清单

简介: 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) 在头文件实用程序中定义 pairpair 是一种异构类型,包含一个具有类型 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_switchcompressed_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_switchcompressed_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,则该类是空的,否则应该看到 。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_pointeris_voidis_array
  • 测试两种类型之间的关系,例如 is_same<T1, T2>is_base_and_derived<T1, T2>
  • 将一种对象转换为另一种,例如 remove_const<T>::type 还将创建与 T 相同的类型,但删除了 const 修饰符,remove_cv<T>::type 将创建与 T 相同的类型,但删除了 constvolatile 修饰符。

要使用 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>::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 给出了类声明。


清单 17. noncopyable 类声明
 				
class noncopyable
  {
   protected:
      noncopyable() {}
      ~noncopyable() {}
   private:  // emphasize the following members are private
      noncopyable( const noncopyable& );
      const noncopyable& operator=( const noncopyable& );
  };

清单 17 中要注意的另一点是,未提供 copy constructoroperator= 方法的定义。如果它们已实现,从技术上讲,可以在 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::swapstd::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[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::swapstd::swap 位于相同范围内,那么对 swap 的调用将优先于 std::swap

结束语

本文的内容到此就结束了。我们从使用的角度分析了一些有趣的实用程序:压缩的 pair、类型特征、不可复制类、自定义断言处理和自定义交换,还尝试着理解了它们的内部原理。严格地说,理解 Boost 头文件的内部原理不是使用这些实用程序所必需的,但知道这些原理会使各种操作变得更轻松。显然,Boost 部署的技巧对于高性能代码和库的开发是不可或缺的。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值