Boost C++ 库

……世界上最受推崇和设计精良的 C++ 库项目之一。 Herb SutterAndrei AlexandrescuC++ 编码规范

boost.png (6897 字节)Boost Format 库

<boost/format.hpp> 中的 format 类提供类似 printf 的格式化功能,以类型安全的方式允许输出用户定义的类型。


概要

格式对象由格式字符串构造,然后通过重复调用operator%来传递参数。
每个参数都会被转换为字符串,然后根据格式字符串将这些字符串组合成一个字符串。

cout << boost::format("writing %1%,  x=%2% : %3%-th try") % "toto" % 40.23 % 50; 
     // prints "writing toto,  x=40.230 : 50-th try"

工作原理

  1. 当你调用format(s)(其中 s 是格式字符串)时,它会构造一个对象,该对象会解析格式字符串并查找其中的所有指令,并为下一步准备内部结构。
  2. 然后,立即(如:
    cout << format("%2% %1%") % 36 % 77;
    
    或稍后(如:
    format fmter("%2% %1%");
    fmter % 36; fmter % 77;
    
    你将变量馈送到格式化程序。
    这些变量被转储到内部流中,其状态根据格式字符串中给定的格式选项(如果有)设置,格式对象存储最后一步的字符串结果。
  3. 一旦所有参数都被馈送,你可以将格式对象转储到流中,或者使用str()成员函数或boost命名空间中的自由函数str(const format& )获取其字符串值。结果字符串将保留在格式对象中,直到传递另一个参数,此时它将被重新初始化。
    // fmter was previously created and fed arguments, it can print the result :
    cout << fmter ;  
    
    // You can take the string result :
    string s  = fmter.str();
    
    // possibly several times :
    s = fmter.str( );
    
    // You can also do all steps at once :
    cout << boost::format("%2% %1%") % 36 % 77; 
    
    // using the str free function :
    string s2 = str( format("%2% %1%") % 36 % 77 );
    
    
  4. 可选地,在步骤 3 之后,你可以重用格式对象并从步骤 2 重新开始:fmter % 18 % 39;
    使用相同的格式字符串格式化新变量,从而节省步骤 1 中涉及的昂贵处理。
总而言之,format 类将格式字符串(可能包含类似 printf 的指令)转换为对内部流的操作,最后以字符串的形式返回格式化结果,或直接写入输出流。

示例

using namespace std;
using boost::format;
using boost::io::group;

示例文件

程序 sample_formats.cpp 演示了format 的简单用法。

sample_new_features.cpp 说明了添加到 printf 语法中的几个格式化特性,例如简单的位置指令、居中对齐和'制表符'。

sample_advanced.cpp 演示了高级特性的用法,例如重用和修改格式对象等。

sample_userType.cpp 显示了format 库在用户定义类型上的行为。


语法

boost::format( 格式字符串 ) % arg1 % arg2 % ... % argN

格式字符串包含文本,其中特殊指令将被替换为由给定参数的格式化生成的字符串。
C 和 C++ 世界中的传统语法是 printf 使用的语法,因此 format 可以直接使用 printf 格式字符串并产生相同的结果(几乎在所有情况下都是如此。有关详细信息,请参见 与 printf 的不兼容性)。
此核心语法已扩展,以允许新的特性,并适应 C++ 流上下文。因此,format 接受格式字符串中几种形式的指令

除了标准 printf 格式说明之外,还实现了新的特性,例如居中对齐。有关详细信息,请参见 新的格式说明

printf 格式说明

Boost.format 支持的 printf 格式说明遵循 Unix98 Open-group printf 的精确语法,而不是标准 C printf(不支持位置参数)。(常用标志在两者中含义相同,因此对任何人来说都不应该是一个难题)
注意,在同一个格式字符串中混合使用位置格式说明(例如%3$+d和非位置格式说明(例如%+d是错误的。
在 Open-group 规范中,多次引用同一个参数(例如"%1$d %1$d")具有未定义的行为。Boost.format 在此类情况下的行为是允许多次引用每个参数。唯一的限制是它期望恰好P个参数,P是格式字符串中使用的最大参数编号。(例如,对于 "%1$d %10$d",P == 10)。
提供多于或少于P个参数将引发异常。(除非另有设置,请参见 异常

说明spec具有以下形式

    [ N$ ] [ flags ] [ width ] [ . precision ] [ argument-type ] conversion-specifier
方括号内的字段是可选的。以下列表逐一解释了这些字段

新的格式规范

与 printf 的行为差异

假设您有变量x1、x2(内置类型,C 的 printf 支持),
以及一个打算通过这种方式与 printf 函数一起使用的格式字符串s
printf(s, x1, x2);

在几乎所有情况下,结果都与以下命令相同
cout << format(s) % x1 % x2;

但是,由于某些 printf 格式规范无法很好地转换为流格式选项,因此 Boost.format 模拟 printf 的方式存在一些明显的缺陷。
无论如何,format 类都应静默忽略不受支持的选项,以便 format 始终接受 printf 格式字符串,并产生与 printf 几乎相同的输出。


以下是此类差异的完整列表另外,请注意,特殊的'n' 类型规范(用于告诉 printf 将格式化输出的字符数保存在变量中)在 format 中无效。
因此,包含此类型规范的 format 字符串应通过 printf 或 format 生成相同的转换字符串。它不会导致 printf 和 format 之间的格式化字符串出现差异。
要使用 Boost.Format 获取格式化字符串中的字符数,可以使用size()成员函数
format formatter("%+5d");
cout << formatter % x;
unsigned int n = formatter.size();

用户定义类型输出

所有转换为流状态修改的标志都在用户定义类型中递归地起作用。(标志保持有效,所需的格式选项也如此,对于用户定义类可能调用的每个“<<”操作都是如此)

例如,对于 Rational 类,我们将拥有类似的内容
Rational ratio(16,9);
cerr << format("%#x \n")  % ratio;  // -> "0x10/0x9 \n"

对于其他格式选项,情况有所不同。例如,设置宽度适用于对象生成的最终输出,而不是其每个内部输出,这很幸运

cerr << format("%-8d")  % ratio;  // -> "16/9    "      and not    "16      /9       "
cerr << format("%=8d")  % ratio;  // -> "  16/9  "      and not    "   16   /    9   "


但“0”和“ ”选项也是如此(与“+”相反,“+”由showpos直接转换为流状态。但零和空格 printf 选项不存在此类标志)
这不太自然

cerr << format("%+08d \n")  % ratio;  // -> "+00016/9"
cerr << format("% 08d \n")  % ratio;  // -> "000 16/9"
可以通过仔细设计 Rational 的operator<<来获得更好的行为,它本身处理流的宽度、对齐和showpos参数。这在sample_userType.cpp中进行了演示。

操纵器和内部流状态

format 的内部流状态在参数输出之前保存,并在之后恢复;因此,修饰符不是粘性的,只影响其应用到的参数。
根据标准,流的默认状态为:精度 6,宽度 0,右对齐,并设置十进制标志。

可以通过与参数一起传递的操纵器来更改内部format流的状态;通过group函数,如下所示

cout << format("%1% %2% %1%\n") % group(hex, showbase, 40) % 50; // prints "0x28 50 0x28\n"


在“group”内部传递 N 个项目时,Boost.format 需要以与常规参数不同的方式处理操纵器,因此使用 group 受以下约束

  1. 要打印的对象必须作为组中的最后一个项目传递
  2. 前 N-1 个项目被视为操纵器,如果它们确实产生输出,则将其丢弃

此类操纵器在每次出现时都会在下一个参数之前传递到流。请注意,格式字符串中指定的格式选项会被以此方式传递的流状态修饰符覆盖。例如,在以下代码中,hex操纵器优先于格式字符串中的d类型规范,该规范将设置十进制输出

cout << format("%1$d %2% %1%\n") % group(hex, showbase, 40) % 50; 
// prints "0x28 50 0x28\n"

替代方案


异常

Boost.format 对 format 对象的使用强制执行许多规则。格式字符串必须符合上面描述的语法,用户必须在输出到最终目标之前提供完全正确的参数数量,如果使用 modify_item 或 bind_arg,则项目和参数索引不得超出范围。
当 format 检测到不满足这些规则之一时,它会引发相应的异常,以使错误不会被忽视和未处理。
但是用户可以更改此行为以适应其需求,并使用以下函数选择哪些类型的错误可能会引发异常

unsigned char exceptions(unsigned char newexcept); // query and set
unsigned char exceptions() const;                  // just query

用户可以使用二进制算术组合以下原子来计算参数newexcept

例如,如果您不希望 Boost.format 检测参数数量错误,您可以定义一个用于构建具有正确异常设置的 format 对象的特定包装器函数

boost::format  my_fmt(const std::string & f_string) {
    using namespace boost::io;
    format fmter(f_string);
    fmter.exceptions( all_error_bits ^ ( too_many_args_bit | too_few_args_bit )  );
    return fmter;
}

然后允许提供比需要更多的参数(它们只是被忽略)
cout << my_fmt(" %1% %2% \n") % 1 % 2 % 3 % 4 % 5;

如果我们在提供所有参数之前请求结果,则结果的相应部分将为空
cout << my_fmt(" _%2%_ _%1%_ \n") % 1 ;
// prints      " __ _1_ \n"


关于性能的说明

可以将 boost::format 用于对少量内置类型参数进行重新排序的格式化的性能与 Posix-printf 的性能以及等效的流手动操作的性能进行比较,以衡量由此产生的开销。结果可能很大程度上取决于编译器、标准库实现以及格式字符串和参数的精确选择。

常见的流实现最终会调用 printf 系列的函数来实际格式化数字。通常,由于重新排序开销(分配存储字符串片段,每个项目格式化时的流初始化……),printf 将明显快于直接流操作。直接流操作将比 boost::format 快——可以预期比率在 2 到 5 或更高之间。

当迭代格式化是性能瓶颈时,可以通过将格式字符串解析为 format 对象,并在每次格式化时复制它,来略微提高性能,方法如下所示

    const boost::format fmter(fstring);
    dest << boost::format(fmter) % arg1 % arg2 % arg3 ;
    

作为性能结果的一个示例,作者测量了使用 4 种不同方法进行迭代格式化的执行时间

  1. posix printf
  2. 手动流输出(到一个虚拟的nullStream流,将字节发送到虚无)
  3. 如上所示,从 const 对象复制的 boost::format
  4. 直接 boost::format 使用

该测试使用 g++-3.3.3 编译,并测量了以下时间(以秒和比率表示)

string     fstring="%3$0#6x %1$20.10E %2$g %3$0+5d \n";
double     arg1=45.23;
double     arg2=12.34;
int        arg3=23;

- release mode :
printf                 : 2.13
nullStream             : 3.43,  = 1.61033 * printf
boost::format copied   : 6.77,  = 3.1784  * printf ,  = 1.97376 * nullStream
boost::format straight :10.67,  = 5.00939 * printf ,  = 3.11079 * nullStream

- debug mode :
printf                 : 2.12
nullStream             : 3.69,  = 1.74057 * printf
boost::format copied   :10.02,  = 4.72642 * printf ,  = 2.71545 * nullStream
boost::format straight :17.03,  = 8.03302 * printf ,  = 4.61518 * nullStream

类接口摘录

namespace boost {

template<class charT, class Traits=std::char_traits<charT> > 
class basic_format 
{
public:
  typedef std::basic_string<charT, Traits> string_type;
  typedef typename string_type::size_type     size_type;
  basic_format(const charT* str);
  basic_format(const charT* str, const std::locale & loc);
  basic_format(const string_type& s);
  basic_format(const string_type& s, const std::locale & loc);
  basic_format& operator= (const basic_format& x);

  void clear(); // reset buffers
  basic_format& parse(const string_type&); // clears and parse a new format string

  string_type str() const;
  size_type size() const;

  // pass arguments through those operators :
  template<class T>  basic_format&   operator%(T& x);  
  template<class T>  basic_format&   operator%(const T& x);

  // dump buffers to ostream :
  friend std::basic_ostream<charT, Traits>& 
  operator<< <> ( std::basic_ostream<charT, Traits>& , basic_format& ); 

   // Choosing which errors will throw exceptions :
   unsigned char exceptions() const;
   unsigned char exceptions(unsigned char newexcept);

// ............  this is just an extract .......
}; // basic_format

typedef basic_format<char >          format;
typedef basic_format<wchar_t >      wformat;


// free function for ease of use :
template<class charT, class Traits> 
std::basic_string<charT,Traits>  str(const basic_format<charT,Traits>& f) {
      return f.str();
}


} // namespace boost

基本原理

此类的目标是带来更好的 C++、类型安全且可扩展类型的printf等效项,可与流一起使用。

准确地说,format旨在提供以下功能

在设计过程中,遇到了许多问题,并做出了一些选择,这些选择可能并不直观正确。但在每种情况下,它们都是出于某些原因而做出的。


鸣谢

Boost format 的作者是 Samuel Krempp。他使用了 Rüdiger Loos 的 format.hpp 和 Karl Nelson 的格式化类的想法。


Valid HTML 4.01 Transitional

修订版2017 年 10 月 23 日

版权所有 © 2002 Samuel Krempp

本软件在 Boost 软件许可证 1.0 版下发行。(许可证文本位于附带文件 LICENSE_1_0.txt 中,您也可以从 https://boost.ac.cn/LICENSE_1_0.txt 获取)