Boost C++ 库

...世界上最受尊敬和精心设计的 C++ 库项目之一。 Herb SutterAndrei Alexandrescu, C++ 编码标准

Boost 实现变体

接口和实现的分离

boost.org 库组件(以及一般高质量软件)的接口规范在概念上与这些接口的实现是分离的。这可能不明显,特别是当一个组件完全在头文件中实现时,但这种接口和实现的分离始终是假设的。从那些关注软件设计、可移植性和标准化的人的角度来看,接口才是重要的,而实现只是一个细节。

Dietmar Kühl,boost.org 的最初贡献者之一,评论道:“主要贡献是接口,它通过一个实现来增强,证明可以实现相应的类,并提供一个免费的实现。”

实现变体

可能需要一个接口的多个实现,以适应平台依赖性或性能权衡。平台依赖性的示例包括编译器缺陷、文件系统、线程机制和图形用户界面。性能权衡的经典示例是使用大量内存的快速实现,与使用较少内存的较慢实现。

Boost 库通常使用一个配置头文件,boost/config.hpp,来捕获编译器和平台依赖性。虽然不强制使用 boost/config.hpp,但它是解决简单配置问题的首选方法。

Boost 策略

Boost 的策略是避免接口规范中出现平台相关的变体,但提供可在广泛平台和应用程序上使用的实现。这意味着 boost 库将使用下面描述的技术来处理平台依赖性。

Boost 对旨在提高性能的实现变体的策略是,除非收益大大超过全部成本,否则应避免使用它们。“全部成本”一词旨在包括有形成本(如额外的维护)和无形成本(如增加用户理解的难度)。

提供实现变体的技术

可以使用多种技术来提供实现变体。每种技术在某些情况下都适用,而在其他情况下则不适用。

单一通用实现

第一种技术是根本不提供实现变体。相反,提供一个单一的通用实现,并放弃所有其他技术所暗示的复杂性增加。

适用:当可以编写在各种平台上具有合理性能的单一可移植实现时。当替代实现仅在深奥方面有所不同时,尤其适用。

不适用:当实现需要特定于平台的功能,或者当存在多种可能具有截然不同的性能特征的实现时。

Beman Dawes 评论说:“在设计讨论中,某些实现通常被认为是比另一种实现快得多,但计时测试发现没有显着差异。教训是,虽然算法差异可能会显着影响速度,但编码差异(例如将类从虚成员更改为非虚成员或删除一层间接寻址)不太可能产生任何可测量的差异,除非在内部循环深处。即使在内部循环中,现代 CPU 通常也会在相同的时钟周期数内执行此类竞争代码序列!单一通用实现通常就足够了。”

或者,正如 Donald Knuth 所说,“过早优化是万恶之源。” (Computing Surveys, vol 6, #4, p 268).

虽然宏的弊端众所周知,但在少数情况下,宏仍然是首选解决方案

  • 通过 #include 保护防止多次包含头文件。
  • 将次要配置信息从配置头文件传递到其他文件。

适用:对于较小的编译时变体,否则安装、使用或维护成本高或令人困惑。更适合在库组件内部和之间进行通信,而不是与库用户进行通信。

不适用:如果有其他技术可以做到。

为了最大限度地减少宏的负面影响

  • 仅当宏明显优于其他技术时才使用它们。它们应被视为最后的手段。
  • 名称应全部大写并以命名空间名称开头。这将最大限度地减少名称冲突的机会。例如,名为 foobar.h 的 boost 头文件的 #include 保护可能被命名为 BOOST_FOOBAR_H。

单独的文件

一个库组件可以有多个变体,每个变体都包含在自己的单独文件或文件中。最合适变体的文件在安装时被复制到相应的包含或实现目录。

在 boost 库中提供此方法的方式是将专门的实现作为单独的文件包含在 .ZIP 发行文件中的单独子目录中。例如,名为 foobar 的库的 .ZIP 发行文件中的结构,该库既有默认变体也有专门变体,可能如下所示

foobar.h                // The default header file
foobar.cpp              // The default implementation file
readme.txt              // Readme explains when to use which files
self_contained/foobar.h // A variation with everything in the header
linux/foobar.cpp        // Implementation file to replace the default
win32/foobar.h          // Header file to replace the default
win32/foobar.cpp        // Implementation file to replace the default

适用:当不同的平台需要不同的实现,或者当可能的实现之间存在主要的性能差异时。

不适用:当在同一安装中使用多个变体更有意义时。

单独的组件

与其拥有单个组件的多个实现变体,不如提供几个单独的组件。例如,Boost 库当前提供 scoped_ptrshared_ptr 类,而不是单个 smart_ptr 类,该类被参数化以区分这两种情况。有几种方法可以选择组件

  • 在编码期间由程序员硬编码。
  • 由程序员编写的运行时逻辑选择(牺牲一些额外的空间、时间和程序复杂性来换取在运行时选择实现的能力。)

适用:当变体的接口不同,并且使用多个变体是合理的时候。当需要运行时选择实现时。

不适用:当变体是数据类型、特征或特化变体,可以通过将组件设为模板来更好地处理时。当变体的选择最好通过程序本身之外的某些设置或安装机制来完成时,也不适用。因此,通常不适用于处理平台差异。

注意:还有一种相关技术,其中接口被指定为抽象(纯虚)基类(或接口定义语言),并且实现选择被传递给第三方,例如动态链接库或对象请求代理。虽然这是一种强大的技术,但它超出了本讨论的范围。

基于模板的方法

将类或函数转换为模板通常是处理变体的优雅方法。基于模板的方法以将实现选择限制为编译时为代价,提供了最佳的空间和时间效率。

重要的模板技术包括

  • 数据类型参数化。这允许单个组件对各种数据类型进行操作,这也是最初发明模板的原因。
  • 特征参数化。如果参数化很复杂,则将各个方面捆绑到单个特征辅助类中可以允许很大的变化,同时隐藏混乱的细节。C++ 标准库提供了此惯用法的几个示例,例如 iterator_traits<> (24.3.1 lib.iterator.traits) 和char_traits<>(21.2 lib.char.traits)。
  • 特化。模板参数可以纯粹用于选择特化。例如
SomeClass<fast>  my_fast_object;  // fast and small are empty classes
SomeClass<small> my_small_object; // used just to select specialization

适用:当需要变化是由于数据类型或特征,或者是与性能相关的(例如在几种算法中选择),并且当程序可以合理地使用多个变体时。

不适用:当变体的接口不同,或者当变体的选择最好通过程序本身之外的某些机制来完成时。因此,通常不适用于处理平台差异。