9.杂项讨论 Miscellany

欢迎来到大杂烩的一章。本章只有3个条款,但千万别被低微的数字或不迷人的布景愚弄了,它们都很重要!

第一个条款强调不可以轻忽编译器警告信息。至少,如果你希望你的软件有适当行为的话,别太轻忽它们。第二个条款带你综览 C++标准程序库,其中覆盖由TR1引进的重大新机能。最后一个条款带你综览 Boost,那是我认为最重要的一个C++泛用型网站。如果你尝试写出高效 C++软件,却没有参考这些条款所提供的信息,那么充其量也只是一场事倍功半的恶战。

条款53:不要轻忽编译器的警告 Pay attention to compiler warnings.

许多程序员习惯性地忽略编译器警告。他们认为,毕竟,如果问题很严重,编译器应该给一个错误信息而非警告信息,不是吗?这种想法对其他语言或许相对无害,但在C++,我敢打赌编译器作者对于将会发生的事情比你有更好的领悟。举个例子,下面是多多少少都会发生在每个人身上的一个错误:

class B {
public:
	virtual void f() const;
};
class D: public B {
public:
	virtual void f();
};

这里希望以D::f重新定义virtual函数B::f,但其中有个错误:B中的f是个const 成员函数,而在D中它未被声明为const。我手上的一个编译器于是这样说话了:

warning: D::f() hides virtual B::f()

太多经验不足的程序员对这个信息的反应是:“噢当然,D::f遮掩了 B::f,那正是想象中该有的事!”错,这个编译器试图告诉你声明于B中的f并未在D中被重新声明,而是被整个遮掩了(条款33描述为什么会这样)。如果忽略这个编译器警告,几乎肯定导致错误的程序行为,然后是许多调试行为,只为了找出编译器其实早就侦测出来并告诉你的事情。

一旦从某个特定编译器的警告信息中获得经验,你将学会了解,不同的信息意味什么——那往往和它们“看起来”的意义十分不同!尽管一般认为,写出一个在最高警告级别下也无任何警告信息的程序最是理想,然而一旦有了上述的经验和对警告信息的深刻理解,你倒是可以选择忽略某些警告信息。不管怎样说,在你打发某个警告信息之前,请确定你了解它意图说出的精确意义。这很重要。

记住,警告信息天生和编译器相依,不同的编译器有不同的警告标准。所以,草率编程然后倚赖编译器为你指出错误,并不可取。例如上述发生“函数遮掩”的代码就可能通过另一个编译器,连半句抱怨和抗议也没有。

请记住

■ 严肃对待编译器发出的警告信息。努力在你的编译器的最高(最严苛)警告级别下争取“无任何警告”的荣誉。

■ 不要过度倚赖编译器的报警能力,因为不同的编译器对待事情的态度并不相同。一旦移植到另一个编译器上,你原本倚赖的警告信息有可能消失。

条款54:让自己熟悉包括TR1在内的标准程序库 Familiarize yourself with the standard library,including TR1.

C++Standard——定义C++语言及其标准程序库的规范——早在1998年就被标准委员会核准了。标准委员会又于2003年发布一个不很重要的“错误修正版”,并预计于2008年左右发布C++Standard 2.0。日期的不确定性使得人们总是称呼下一版C++为 "C++0x",意指200x版的C++。

C++0x 或许会覆盖某些有趣的语言新特性,但大部分新机能将以标准程序库的扩充形式体现。如今我们已经能够知道某些新的程序库机能,因为它被详细叙述于一份称为TR1的文档内。TR1代表 "Technical Report 1",那是C++程序库工作小组对该份文档的称呼。标准委员会保留了TR1被正式铭记于C++0x之前的修改权,不过目前已不可能再接受任何重大改变了。就所有意图和目标而言,TR1宣示了一个新版C++的来临,我们可能称之为Standard C++1.1。不熟悉TR1机能而却奢望成为一位高效的C++程序员是不可能的,因为TR1提供的机能几乎对每一种程序库和每一种应用程序都带来利益。

在概括论述TR1有些什么之前,让我们先回顾一下C++98列入的C++标准程序库有哪些主要成分:

■ STL(Standard Template Library,标准模板库),覆盖容器(containers如vector,string,map)、迭代器(iterators)、算法(algorithms如find,sort,transform)、函数对象(function objects 如 less,greater)、各种容器适配器(container adapters如stack,priority_queue)和函数对象适配器(function object adatpers如mem_fun,not1)。

■ Iostreams,覆盖用户自定缓冲功能、国际化I/O,以及预先定义好的对象cin,cout,cerr和clog。

■ 国际化支持,包括多区域(multiple active locales)能力。像wchar_t(通常是16 bits/char)和wstring(由wchar_ts组成的strings)等类型都对促进Unicode有所帮助。

■ 数值处理,包括复数模板(complex)和纯数值数组(valarray)。

■ 异常阶层体系(exception hierarchy),包括base class exception及其derived classes logic_error和runtime_error,以及更深继承的各个classes。

■ C89标准程序库。1989 C标准程序库内的每个东西也都被覆盖于C++内。

如果你对上述任何一项不很熟悉,我建议你好好排出一些时间,带着你最喜爱的C++书籍,把情势扭转过来。

TR1详细叙述了14个新组件(components,也就是程序库机能单位),统统都放在std命名空间内,更正确地说是在其嵌套命名空间tr1内。因此,TR1组件shared_ptr的全名是std::tr1::shared_ptr。本书通常在讨论标准程序库组件时略而不写std::,但我总是会在TR1组件之前加上tr1::。

本书展示以下TR1组件实例:

 ■ 智能指针(smart pointers)tr1::shared_ptr和 tr1::weak_ptr。前者的作用有如内置指针,但会记录有多少个tr1::shared_ptrs共同指向同一个对象。这便是所谓的reference counting(引用计数)。一旦最后一个这样的指针被销毁,也就是一旦某对象的引用次数变成 0,这个对象会被自动删除。这在非环形(acyclic)数据结构中防止资源泄漏很有帮助,但如果两个或多个对象内含tr1::shared_ptrs并形成环状(cycle),这个环形会造成每个对象的引用次数都超过0——即使指向这个环形的所有指针都已被销毁(也就是这一群对象整体看来已无法触及)。这就是为什么又有个 tr1::weak_ptrs 的原因。tr1::weak_ptrs 的设计使其表现像是“非环形tr1::shared_ptr-based数据结构”中的环形感生指针(cycle-inducing pointers)。tr1::weak_ptrs 并不参与引用计数的计算;当最后一个指向某对象的 tr1::shared_ptr被销毁,纵使还有个tr1::weak_ptrs继续指向同一对象,该对象仍旧会被删除。这种情况下的tr1::weak_ptrs会被自动标示无效。

tr1::shared_ptr或许是拥有最广泛用途的T R 1组件。本书多次使用它,条款13解释它为什么如此重要。本书并未示范使用tr1::weak_ptr,抱歉。

■ tr1::function,此物得以表示任何callable entity(可调用物,也就是任何函数或函数对象),只要其签名符合目标。假设我们想注册一个 callback 函数,该函数接受一个int并返回一个string,我们可以这么写:

void registerCallback(std::string func(int));//参数类型是函数,该函数接受一个int并返回一个string

其中参数名称func可有可无,所以上述的registerCallback也可以这样声明:

void registerCallback(std::string (int));//与上同:能数名称略而未写

注意这里的 "std::string (int)"是个函数签名。

tr1::function使上述的RegisterCallback有可能更富弹性地接受任何可调用物(callable entity),只要这个可调用物接受一个int或任何可被转换为int的东西,并返回一个 string或任何可被转换为 string的东西。tr1::function是个template,以其目标函数的签名(target function signature)为参数:

void registerCallback(std::tr1::function<std::string (int)> func);
//参数“func”接受任何可调用物(callable entity)
//只要该“可调用物”的签名与"std::string (int)"一致

这种弹性真令人惊讶,我尽最大的努力在条款35示范了它的用法。

■ tr1::bind,它能够做STL绑定器(binders)bind1st和bind2nd所做的每一件事,而又更多。和前任绑定器不同的是,tr1::bind可以和const及non-const成员函数协同运作,可以和by-reference参数协同运作。而且它不需特殊协助就可以处理函数指针,所以我们调用 tr1::bind 之前不必再被什么ptr_fun,mem_fun或mem_fun_ref搞得一团混乱了。简单地说,tr1::bind是第二代绑定工具(binding facility),比其前一代好很多。我在条款35示范过它的用法。

我把其他TR1组件划分为两组。第一组提供彼此互不相干的独立机能:

■ Hash tables,用来实现sets,multisets,maps和multi-maps。每个新容器的接口都以其前任(TR1 之前的)对应容器塑模而成。最令人惊讶的是它们的名称:tr1::unordered_set,tr1::unordered_multiset,tr1::unordered_map 以及tr1::unordered_multimap。这些名称强调它们和set,multiset,map 或multimap不同:以hash为基础的这些TR1容器内的元素并无任何可预期的次序。

■ 正则表达式(Regular expressions),包括以正则表达式为基础的字符串查找和替换,或是从某个匹配字符串到另一个匹配字符串的逐一迭代(iteration)等等。

■ Tuples(变量组),这是标准程序库中的pair template的新一代制品。pair只能持有两个对象,tr1::tuple可持有任意个数的对象。漫游于Python和Eiffel的程序员,额手称庆吧!你们前一个家园的某些好东西现在已经纳入C++。

■ tr1::array,本质上是个“S T L化”数组,即一个支持成员函数如begin和end的数组。不过tr1::array的大小固定,并不使用动态内存。

■ tr1::mem_fn,这是个语句构造上与成员函数指针(member function pointers)一致的东西。就像tr1::bind 纳入并扩充C++9 8的bind1st和bind2nd的能力一样,tr1::mem_fn纳入并扩充了C++98的mem_fun和mem_fun_ref的能力。

■ tr1::reference_wrapper,一个“让 references 的行为更像对象”的设施。它可以造成容器“犹如持有references”。而你知道,容器实际上只能持有对象或指针。

■ 随机数(random number)生成工具,它大大超越了rand,那是C++继承自C标准程序库的一个函数。

■ 数学特殊函数,包括Laguerre多项式、Bessel函数、完全椭圆积分(complete elliptic integrals),以及更多数学函数。

■ C99 兼容扩充。这是一大堆函数和模板(templates),用来将许多新的 C99 程序库特性带进C++。

第二组 TR1 组件由更精巧的 template 编程技术(包括 template metaprogramming,也就是模板元编程,见条款48)构成:

■ Type traits,一组traits classes(见条款47),用以提供类型(types)的编译期信息。给予一个类型T,TR1的type traits可以指出T是否是个内置类型,是否提供virtual析构函数,是否是个empty class(见条款39),可隐式转换为其他类型U吗……等等。TR1的type traits也可以显现该给定类型之适当齐位(proper alignment),这对定制型内存分配器(见条款50)的编写人员是十分关键的信息。

■ tr1::result_of,这是个 template,用来推导函数调用的返回类型。当我们编写templates时,能够“指涉(refer to)函数(或函数模板)调用动作所返回的对象的类型”往往很重要,但是该类型有可能以复杂的方式取决于函数的参数类型。tr1::result_of使得“指涉函数返回类型”变得十分容易。它也被TR1自身的若干组件采用。

虽然若干TR1成分(特别是tr1::bind和tr1::mem_fn)纳入了某些“前TR1”组件能力,但其实TR1是对标准程序库的纯粹添加,没有任何TR1组件用来替换既有组件,所以早期(写于TR1之前的)代码仍然有效。

TR1自身只是一份文档[1]。为了取得它所规范的那些机能,你还需要取得实现代码。这些代码最终会随编译器出货。在我下笔的 2005 年此刻,如果你在你手上的标准程序库实现版本内寻找TR1组件,极可能有某些遗漏。幸运的是你可以补齐它们:TR1的14个组件中的10个奠基于免费的Boost程序库(见条款55),所以对TR1-like机能而言,Boost是个绝佳资源。我说 "TR1-like" 是因为虽然许多TR1机能奠基于Boost程序库,但毕竟有些Boost机能并不完全吻合TR1规范。当你阅读这一段文字,说不定 Boost 已经不只提供与 TR1 一致的实现(对于那些奠基于Boost程序库的10个TR1组件),还供应4个不以Boost为基础的TR1组件实现。

在编译器附带TR1实现品的那一刻到来之前,如果你喜欢以Boost的TR1-like程序库作为一时权宜,或许你会愿意以一个命名空间上的小伎俩让自己将来好过些。所有Boost组件都位于命名空间boost内,但TR1组件都置于std::tr1内。你可以这样告诉你的编译器,令它对待references to std::tr1就像对待references toboost一样:

namespace std {
	namespace tr1 = ::boost;//namespace std::tr1是namespace boost的一个别名
}

纯就技术而言,这简直是把你流放到“未定义行为”的国土去了,因为就如条款25所言,任何人不得加任何东西到 std命名空间去。然而实际上你很可能不会有任何麻烦。一旦将来你的编译器提供它们自己的TR1实现品,你需要做的唯一事情就是消除上述的namespace别名,而后指涉std::tr1的代码继续生效,好极了。

非以Boost程序库为基础的那些TR1组件之中,最重要的或许是hash tables。其实hash tables早已行之有年,分别以名称hash_set,hash_multiset,hash_map和hash_multimap为人熟知。也许你的编译器已经附带那些templates实现码。如果没有,请启动你最喜欢的查找引擎,查找那些名称(及其TR1称号),你一定可以找到若干来源,包括商业产品和免费产品。

请记住

■ C++标准程序库的主要机能由STL、iostreams、locales组成。并包含C99标准程序库。■ TR1添加了智能指针(例如 tr1::shared_ptr)、一般化函数指针(tr1::function)、hash-based容器、正则表达式(regular expressions)以及另外10个组件的支持。

■ TR1自身只是一份规范。为获得TR1提供的好处,你需要一份实物。一个好的实物来源是Boost。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值