Boost C++ 库

...世界上最受推崇和专业设计的 C++ 库项目之一。 Herb SutterAndrei Alexandrescu, C++ 编码标准

boost.png (6897 bytes)Boost Format 库

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


概要

format 对象从格式字符串构造,然后通过重复调用 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;
    
    您将变量馈送到格式化程序中。
    这些变量被转储到内部流中,该流的状态根据格式字符串中给定的格式化选项(如果有的话)进行设置,并且 format 对象存储最后一步的字符串结果。
  3. 一旦所有参数都被馈送,您可以将 format 对象转储到流中,或者通过使用 str() 成员函数或命名空间 boost 中的自由函数 str(const format& ) 获取其字符串值。结果字符串在 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 之后,您可以重用 format 对象并从步骤 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 演示了高级功能的使用,例如重用和修改 format 对象等。

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 支持),
以及一个格式字符串 s,旨在通过这种方式与 printf 函数一起使用
printf(s, x1, x2);

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

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


这是此类差异的完整列表另请注意,特殊的 'n' 类型规范(用于告诉 printf 在变量中保存格式化输出的字符数)在 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 复制)