读书笔记 effective c++ Item 54 让你自己熟悉包括TR1在内的标准库

1. C++0x的历史渊源

C++标准——也就是定义语言的文档和程序库——在1998被批准。在2003年,一个小的“修复bug”版本被发布。然而标准委员会仍然在继续他们的工作,一个“2.0版本”的C++标准预计在2009年被发布(虽然所有的工作很有可能在2007年底被完成)。直到

在,发布下一版C++的预计年份还没有被确定,这就解释了为什么人们把下一版C++叫做“C++0x”——C++的200x年版本。

C++0x可能会包含一些有趣的新的语言特性,但是大多数新C++功能将会以标准库附加物的形式被发布。我们已经知道了一些新的库功能将会是什么,因为它们已经在文档TR1(来自C++库工作组的Technical Report 1)中被指定了。在C++0x被官方正式发布之前,标准委员会保有对TR1的功能进行修改的权利,但是不太可能有大的修改。TR1预示了一个新的C++ release的开始——我们可能将其叫做标准C++ 1.1.如果你不熟悉TR1的功能,你不能被称作一个effective C++程序员,因为TR1中的功能对于各个种类的库和应用来说都是一种福利。

2. C++98标准库中都有什么?

在考察什么是TR1之前,回顾一下C++98版本标准库中的主要部分是很有价值的:

  • 标准模版库(STL),包含容器(vector,string,,map等等);迭代器,算法(find,sort,transform等等);函数对象(less,greater等等);还有不同的容器和函数对象适配器(stack,priority_queue,mem_fun,not1等)
  • Iostreams,包括对用户自定义buffering的支持,国际化IO,和预定义对象cin,cout,cerr和clog.
  • 支持国际化,包括多区域的能力(multipue active locales)。像类型wchar_t(通常是16bits/char)和wstring(wchar_t组成的string)能够促进同Unicode一块工作。
  • 支持对数值的处理,包括复杂数(complex)模板和纯数数组(valarray)。
  • 异常继承体系,包括基类异常,派生类logic_error和runtime_error,还有继承自这些类的其他类。
  • C89的标准库。在1989 C标准库中的所有东西同样被放入了C++。

如果你对上面的任何条款不熟悉,我建议你抽出足够的时间来看一些c++参考读物。

3. TR1中都包含什么?

TR1提出了14个新的组件(也就是程序库功能片段(pieces))。所有都被放入std命名空间中,更精确的说,是在内嵌命名空间tr1中。TR1组件 shared_ptr的全称因此就为std::tr1::shared_ptr。在这本书中,当讨论标准库的组件时,我通常会省略std::,但是我总是会为TR1组件加上前缀tr1::。

本书举例说明TR1中的一些组件:

  • 智能指针 tr1::shared_ptr**和tr1::weak_ptr。Tr1::shared_ptr的行为表现就像内建指针一样,但是它们追踪了有多少个tr1::shared_ptr指针指向一个对象。这被叫做引用计数**。当最后的指针被销毁(也就是对象的引用计数变为0的时候),对象自动被delete。这在非环状数据结构中用于防止资源泄漏很好,但如果两个或者多个对象包含tr1::shared_ptr,这样一个环就形成了,这个循环可能相互持有对方对象的引用计数,而且都大于0——即使当所有的环的外部指针被销毁了(也就是当作为一个整体的对象组不能被使用了)。这时候就得使用tr1::weak_ptr,tr1::weak_ptr被设计为在非环状tr1::shared_base数据结构中的cycle-inducing 指针。Tr1::weak_ptr中没有引用计数。当指向对象的最后一个shared_ptr被销毁时,对象就会被delete,即使tr1::weak_ptr仍然指向这个对象。然而这样的指针会被自动被标记为失效。

Tr1::shared_ptr可能是TR1中最被广泛使用的对象。我在这本书中也使用了多次,包括在Item 13中,在这个条款中我解释了为什么它如此重要。(这本书没有weak_ptr的使用)

  • Tr1::function,使用它可以表示任意可调用实体(例如,任何函数或者函数对象),只要这些实体的签名同目标签名是一致的。如果我们想使用它来注册一个回调函数,这个函数用int作为参数并且返回值为string。我们可以这么做:
1
2
3
void registerCallback(std::string func(int)); // param type is a function
// taking an int and
// returning a string

参数名字 func是可选的,所以registerCallback可以被声明为如下:

1
2
void registerCallback(std::string (int)); // same as above; param
// name is omitted

注意在这里“std::string(int)”为函数签名。Tr1:;function可以使registerCallback更加灵活,它可以接受任何可调用实体作为它的参数,这个调用实体使用int或者可以转换为Int的任何东西作为参数,返回值可以为一个string或者可以转换为string的任何东西。Tr1::function使用目标函数签名作为模板参数:

1
2
3
4
5
void registerCallback(std::tr1::function<std::string (int)> func);
// the param “func” will
// take any callable entity
// with a sig consistent
// with “std::string (int)”

这种灵活性非常有用,我已经在Item 35中展示过了。

  • Tr1::bind,它能做STL绑定器bind1st和bind2nd能做的所有事情,甚至更多。不像pre-TR1中的binders,tr1::bind可以工作在const和非const成员函数中;可以使用按引用传递的参数;可以在没有其他函数帮助的情况下处理函数指针,所以在调用tr1::bind之前就没有必要同ptr_fun,mem_fun或者mem_fun_ref掺杂在一起了。简单说,tr1::bind是第二代绑定工具,它要远远好于第一代。我已经在Item 35中进行了举例。

我将剩下的TR1组件分成两部分。第一部分提供了相当独立的功能:

  • Hash table 被用来实现set,multiset,map和multimap。每个新的容器都将模拟与pre-TR1相对应部分的接口。对于TR1中的hash table,最让人感到意外的是它们的名字:tr1::unordered_set,tr1::unordered_multiset,tr1::unordered_map和tr1::unordered_multimap。这些名字强调了它的内容不会像set,multiset或者multimap一样,TR1中hash-based的容器中的元素顺序是无序的。
  • 正则表达式,包括在字符串上进行的基于正则表达式的搜索和替换功能,还有从一个匹配字符串到另一个匹配字符串的迭代等等。
  • Tuple,它是对已经在标准库中存在的pair模板的泛化。相比于Pair对象会持有两个对象,tr1::tuple对象能够持有任意数量的对象。
  • Tr1::array,本质上来说是一个“STL化的“数组,也就是一个支持像begin和end这样的成员函数的数组。Tr1::array的大小在编译期被固定;对象不使用动态内存。
  • Tr1::mem_fn,为成员函数指针进行适配的在句法上的一个统一的方式。就像tr1::bind把C++98的bind1st和bind2nd的功能包含进来并对其进行扩展,tr1::mem_fn把C++98中的mem_fun和mem_fun_ref的功能包含进来并对其进行了扩展。
  • Tr1::reference_wrapper,这是一个功能使得引用的行为表现就像对象一样。这使得创建一个行为表现就如同持有引用的容器成为可能(事实上,容器只能包含对象或者指针。)
  • 随机数生成器(Random number generation)功能要比从C标准库中继承而来的随机函数更加优秀。
  • 数学特殊函数(Mathematical special function),包括拉盖尔多项式,贝塞尔函数,完全椭圆积分(complete elliptic integrals)等等。
  • C99兼容性扩展,为了将许多新的C99程序库的功能引入到C++中而设计的函数集合与模板。

TR1组件的第二个集合由为更加复杂的模板编程技术提供的支撑技术所组成,包括模板元编程(Item 48):

  • 类型特性(Type traits),提供了一系列trait类(见Item 47)来为类型提供编译时信息。给定一个类型T,TR1的类型特性能够揭示T是否是一个内建类型,能否提供虚析构函数,是否是一个empty class(Item 39),是否可以隐式的转换为其它类型U,等等。TR1中的type traits同样也能够为一个类型揭示合适的对齐问题(alignment),这就为实现自定义内存分配函数的程序员提供了重要信息(Item 50)。
  • Tr1::result_of,一个用来推导函数返回类型的模板。当实现模板的时候,能够引用从函数(模板)调用中返回回来的对象类型很重要,但是返回类型可以以复杂的方式来依赖函数的参数。在TR1中很多地方都使用到了Tr1::result_of。

虽然TR1中的一些功能(尤其是tr1::bind和tr1::mem_fn)只是将pre-TR1的一些组件纳入其中,但TR1只是标准库的额外添加物。没有TR1组件是对现存组件的替换,所以使用pre-TR1构建的遗留代码仍然有效。

4. 从哪里找到TR1实现

TR1本身只是一个文档。为了使用它指定的功能,你需要访问实现这些功能的代码。这些代码最后将会同编译器捆绑在一块发布,但是我写这本书是在2005年,如果在你的标准库实现中寻找TR1组件,可能会有一些遗漏。幸运的是,可以从其他地方进行搜寻:TR1 的14个组件中的10个是基于可以免费获得的Boost库(见Item 55)来实现的,所以如果你想了解和TR1类似(TR1-like)的功能,这会是一个很好的资源。这里我说“TR1-like”,因为虽然很多TR1功能是基于Boost库的,有一些地方Boost功能还没有同TR1规格完全匹配。很有可能但你读到这本书的时候,不仅对于从Boost 库进化而来的TR1组件,Boost中有了与其一致的实现,而且它同时提供了没有基于Boost的其余4个TR1组件的实现。

如果作为权宜之计你想使用Boost中的类似TR1的库,直到编译器同TR1实现一同被发布,你可能会使用一个命名空间的技俩。所有的Boost组件是在命名空间boost中,但是TR1组件将会在命名空间std::tr1中。你可以告诉编译器,把对std::tr1的引用当作对boost的引用来处理。像下面这样:

1
2
3
namespace std {
namespace tr1 = ::boost; // namespace std::tr1 is an alias
} // for namespace boost

从技术上来说,这会让你进入未定义行为的领域,因为正如在Item 25中解释的,不允许向std命名空间中添加任何东西。在实际情况下,看上去你不会遇到任何麻烦。当你的编译器提供了它们自己的TR1实现的时候,所有你需要做的就是移除上面的命名空间别名;引用std::tr1的代码仍然是有效的。

可能没有基于Boost库实现的TR1中的最重要的部分就是hash table了,但是hash tables已经存在很多年了,它们以hash_set,hash_multiset,hasp_map和hash_multimap命名。有可能你的编译器自带的库中已经包含这些模板了。如果没有,使用你最喜欢的搜索引擎去搜一下这些名字,因为你肯定能够找到一些源代码,无论是商业的还是免费的。

5. 总结

  • 主要的标准C++库的功能包括STL,iostream和locales。C89标准库也被包含在内。
  • TR1中添加了对智能指针的支持,广义的函数指针(tr1::function),hash-based 容器,正则表达式和10个其他的组件。
  • TR1本身只是一个说明书。为了使用TR1,你需要一份实现。TR1组件实现的一份源码来自于Boost.