boost.png (6897 bytes)The Boost Format library

<boost/format.hpp> 中的format類提供了類'printf'的格式化,它以類型安全的方式實現允許輸出用戶自定義的類型。


大綱

一個format對像從一個格式化字符串構造,它以重複的%操作符給出參數。接著每個參數轉換成字符串,它們被按照格式合成一個字符串。

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;
    你將變量傳送給格式化器。
    這些變量被存進一個內部stream,它的狀態按照格式化字符串中的格式化選項來設定(如果有的話)。Format對像保留最後的字符串結果。
  3. 一旦是所有的參數都已經傳送給格式化對象了,你可以把格式化對像存進一個stream中,可以用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. 可選的,第三步之後,你可以重用format對象並且在第二步重新開始: fmter % 18 % 39; 用格式化字符串格式化新的變量,保存有關第一步的複雜的處理。
總而言之,format類把一個格式化字符串轉換成(最後使用類prinf指示)對一個內部stream的操作,最後以字符串格式返回,或者直接輸出到一個輸出流。

例子

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

示例文件

程序 sample_formats.cpp 示例了format的一些簡單應用。

sample_new_features.cpp 舉例說明新增的格式化特徵,它們以prinft的語法格式,比如簡單定位,中間對齊和製表符功能。

sample_advanced.cpp 舉例說明format高級特性,比如重用,修改格式化對像等等。

以及 sample_userType.cpp 示例了對於用戶自定義類型中format庫的行為。


語法

boost::format( format-string ) % arg1 % arg2 % ... % argN

格式化字符串包含的字符文本,在它當中特殊的指示器將被字符串所替換,這些字符串由所給的參數產生。
cc++世界繼承來的語法其中之一是使用printf,這樣format能直接使用printf的格式化字符串,並且產生一樣的結果(幾乎包含所有方面,細節請查看 Incompatibilities with printf)。
這些核心語法已被擴展,來允許新特性,但是同時適合c++流的上下文。所以,format接受在格式化字符串中的一系列的指示:

在標準printf格式化規格之上,新的特性被增加進來了,比如居中對齊。細節參看 new format specification .

printf 格式化規則

Boost.format所支持的格式化規則嚴格按照Unix98的文檔 Open-group printf,而非標準cprintf,它不支持有位置信息的參數。(普通的標誌在前兩者中有同樣的含義,這樣都不會任何人帶來麻煩)。
注意:在同一個格式化字符串中同時使用有位置信息(e.g. %3$+d)的格式化規則和沒有位置信息(e.g. %+d)的格式化規則是會出錯的。
Open-group規則中,使用同一參數多次(e.g. "%1$d %1$d")會導致未定義的行為。Boost.format在這些情況中允許多次使用同一參數。唯一的約束是它期望準確的參數個數,P是格式化用到的最大參數個數(e.g., for "%1$d %10$d", P = 10)。
如果提供多餘或者少於
P數量的參數會引起異常(除非它被另外設定,查看 exceptions異常 一節)



一個規範的spec有這樣的格式: [ N$ ] [ flags ] [ width ] [ . precision ] type-char

方括號中的參數是可選的。下面將一一介紹每個參數域:

新的格式規則

與 printf 不兼容的地方

假設你有變量x1,x2(cprintf所支持的內建類型),一個格式化字符串s取代下面的prinft函數用法:
printf(s, x1, x2);

在幾乎所有情況下,結果將和下面的命令一樣:
cout << format(s) % x1 % x2;

但是因為有些printf格式化規則沒有對應到stream格式化選項中,Boost.formatprintf比有一些明顯的不足之處。無論如何,format類應該悄悄的忽略所有不支持的選項,這樣printf格式化串總是能被format接受並輸出幾乎和printf一樣的結果。


下面完整地列出了它們的不同之處: 同樣的,注意到特殊的’n’類型規則(用來告訴printf保存format輸出的字符數)在format中沒有用。所以格式化字符串包含這個類型規則要產生跟用printfformat轉換來的同樣的字符串。它不會導致printfformat格式化來的字符串的不同。使用boost.format來取得格式化字符串字符數,你可以用size()成員函數:
format formatter("%+5d");
cout << formatter % x;
unsigned int n = formatter.size();

用戶自定義類型的輸出

stream上所有標誌的更改都會遞歸的作用在用戶自定義的類上。(標誌將保持活動狀態,同時期望的format選項也一樣,因為’<<’操作可能被用戶自定義的類調用)

比如:有一個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直接改變stream的狀態。但是printf沒有相應於0和空格的選項)那樣不是很自然:

cerr << format("%+08d \n")  % ratio;  // -> "+00016/9"
cerr << format("% 08d \n") % ratio; // -> "000 16/9"
通過仔細設計Rational<<操作來通過它自己傳遞流的寬度,對齊和showpos參數獲取更好的行為是可能的。它在 sample_userType.cpp 中有舉例說明。

操縱子和流內部狀態

Format的內部流狀態被預先保存並在參數輸出後復原;因此,修改量不持久且只應用於一個參數。流的默認狀態,以標準狀態,是:精度為6,寬度為0,並且decimal標誌被設定。

通過參數format流內部狀態能被操縱子所修改,通過group函數,像:

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


當傳遞’group’中的N項,Boost.format需要執行從不同於正則表達式的參數操縱子,因此使用group受到下面的約束:

  1. 要被打印的對象必須作為group中的最後一項
  2. 開始的N-1項作為操縱子來對待,如果它們確實產生輸出,它被丟棄

這樣的操縱子每次在下面的參數前被傳遞給流。注意格式化選項通過以這種形式傳入的重寫了的流狀態修改器被格式化字符串內部所解釋。比如下例所說,格式化字符串中hex控制器擁有比d類型描述更高的優先級,它會設置小數輸出:

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

選擇


異常

Boost.format 遵循一套使用格式化對象的規則。格式化字符串遵守上面描述的語法,用戶在最終輸出目標前必須提供準確的參數數,如果使用modify_item bind_arg,選項和參數索引不能超出範圍。
format發現下面中的一條規則不滿足時,它會引出一個相關的異常,因此錯誤不會被忽略和不被處理。但是用戶可以修改這個行為來滿足他的需要,當異常發生時使用下面的函數來選擇一個錯誤:

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

用戶可以通過使用布爾算術計算來合併下面項從而計算參數 newexcept :

舉例,如果你不想要Boost.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 倍或更多)。

當迭代格式化成為性能瓶頸時,可以通過把格式化字符串分析為一個格式化對像和每次格式化時拷貝它來提高性能。如下所示。

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

作為性能結果的例子,作者用四個方法測量了迭代個格式化的時間

  1. posix printf
  2. 手動流輸出(到一個虛假的空流中,把傳進去的字節忽略掉)
  3. boost::format const對像拷貝,如上所示。
  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_t;
typedef typename string_t::size_type size_type;
basic_format(const charT* str);
basic_format(const charT* str, const std::locale & loc);
basic_format(const string_t& s);
basic_format(const string_t& s, const std::locale & loc);
basic_format& operator= (const basic_format& x);

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

string_t 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而能為stream所用的類。

準確的說,format被設計來提供以下的特性:

在設計的過程中面對很多爭議,作出了一些選擇。這些未必是直觀上很正確。但是任何一種情況它們被採用總是 some reasons 的。


Credits

The author of Boost format is Samuel Krempp.   He used ideas from Rüdiger Loos' format.hpp and Karl Nelson's formatting classes.


Valid HTML 4.01 Transitional

Revised 02 December, 2006

Copyright © 2002 Samuel Krempp

Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)