轉載出處:http://www.ibm.com/developerworks/cn/aix/library/au-util_boost_lib/
實用工具類(utility classes)在幾乎任何適當規模的 C++ 項目中都是必需的,但是不存在滿足此需求的標準方法。通常,團隊根據他們的需求編寫實用工具類代碼,但是由於缺少重要的接口信息,使得相同組織中的其他項目團隊無法重用那些類。建議的標準模板庫(Standard Template Library,STL)只具有諸如 hash
、stack
和 vector
等少數基本類,因此無法有效地用於取代遺留實用工具庫。
本文將介紹幾個 Boost 實用工具類,包括 tuple
、static_assert
、pool
、random
和 program_options
。您需要對標準 STL 具備一定的瞭解才能充分理解本文的內容。本文中的所有代碼都已使用 Boost 1.35 來進行了測試並使用 gcc-3.4.4 來進行了編譯。
有時,您希望 C++ 函數返回多個不相關的值。在推出 STL 之前,實現此目的的方法是創建所有不相關變量的結構,並以指針或引用的形式返回它們或作爲參數傳遞給函數——但是任一種方法都不是表達程序員意圖的方法。STL 引入了 pair
,可將其用於聚合不相關的數據部分,但它一次只能處理兩個數據對象。爲了使用 int
、char
和 float
的元組(tuple ),您可以按如下方式返回 pair
:
make_pair<int, pair<char, float> > (3, make_pair<char, float> ('a', 0.9)); |
隨着您添加更多的元素,創建元組結構將變得越來越困難。Boost tuple
類型派上了用場。要使用 boost::tuple
,您必須包括頭文件 tuple.hpp。要執行元組比較和元組 I/O,您需要分別包括 tuple_comparison.hpp 和 tuple_io.hpp。
清單 1 使用 int
、char
和 float
的元組並打印內容。
#include <iostream> #include <tuple.hpp> #include <tuple_comparison.hpp> #include <tuple_io.hpp> using namespace boost; int main ( ) { tuple<int, char, float> t(2, 'a', 0.9); std::cout << t << std::endl; return 0; } |
此代碼的輸出爲 (2 a 0.9)
。請注意,<<
運算符重載 std::ostream
,以便通過轉儲每個單獨的 tuple
元素來輸出元組。
在使用元組時,務必牢記以下事實:
- 能夠形成元組的元素數量目前僅限於 10 個。
- 元組可以包含用戶定義的類類型,但是您必須負責確保那些類已經定義了正確的構造函數和拷貝構造函數 (copy constructor)。清單 2 顯示了產生編譯時錯誤的代碼部分,因爲該拷貝構造函數是私有的。
清單 2. 用於元組的類必須具有正確的拷貝構造函數#include <tuple.hpp> #include <tuple_comparison.hpp> #include <tuple_io.hpp> #include <iostream> using namespace std; class X { int x; X(const X& u) { x = u.x; } public: X(int y=5) : x(y) { } }; int main ( ) { boost::tuple<int, X> t(3, X(2)); return 0; }
- 與 STL 提供的
make_pair
函數非常類似,Boost 提供了make_tuple
例程。要從函數返回元組,您必須調用make_tuple
。可以創建具有臨時元素的元組;清單 3 的輸出爲(4 0)
。
清單 3.使用make_tuple
來從函數返回元組#include <tuple.hpp> #include <tuple_comparison.hpp> #include <tuple_io.hpp> #include <iostream> using namespace std; boost::tuple<int, int> divide_and_modulo(int a, int b) { return boost::make_tuple<int, int> (a/b, a%b); } int main ( ) { boost::tuple<int, int> t = divide_and_modulo(8, 2); cout << t << endl; return 0; }
- 要訪問元組的各個元素,您可以使用
get
例程。此例程具有兩種變體,如清單 4 所示。請注意,還可以使用get
例程來設置元組的各個元素,雖然有些編譯器可能不支持此功能。
清單 4. 使用boost::get
例程#include <tuple.hpp> #include <tuple_comparison.hpp> #include <tuple_io.hpp> #include <iostream> using namespace std; boost::tuple<int, int> divide_and_modulo(int a, int b) { return boost::make_tuple<int, int> (a/b, a%b); } int main ( ) { boost::tuple<int, int> t = divide_and_modulo(8, 2); cout << t.get<0>() << endl; // prints 4 cout << boost::get<1>(t) << endl; // prints 0 boost::get<0>(t) = 9; // resets element 0 of t to 9 ++boost::get<0>(t); // increments element 0 of t cout << t.get<1>() << endl; // prints 10 return 0; }
- 可以使用
const
限定符來聲明元組,在這種情況下,用於訪問特定元素的get
調用將返回對const
的引用。不能對以這種方式訪問的元素進行賦值(請參見清單 5)。
清單 5. 使用const
限定符來聲明的元組不可修改#include <tuple.hpp> #include <tuple_comparison.hpp> #include <tuple_io.hpp> #include <iostream> using namespace std; int main ( ) { const boost::tuple<int, char*> t(8, "Hello World!"); t.get<1>()[0] = "Y"; // error! boost::get<0>(t) = 9; // error! return 0; }
- 可以使用關係運算符
==
、!=
、<
、>
、<=
和>=
對相同長度的元組進行比較。比較不同長度的元組會產生編譯時錯誤。這些運算符的工作原理是從左到右地比較兩個參與元組的每個單獨的元素(請參見清單 6)。
清單 6. 關係運算符與元組#include <tuple.hpp> #include <tuple_comparison.hpp> #include <tuple_io.hpp> #include <iostream> #include <string> using namespace std; int main ( ) { boost::tuple<int, string> t(8, string("Hello World!")); boost::tuple<int, string> t2(8, string("Hello World!")); cout << (t == t2) << endl; boost::tuple<int, string> r(9, string("Hello World!")); boost::tuple<int, string> r2(8, string("Hello World!")); cout << (r > r2) << endl; boost::tuple<string, string> q(string("AA"), string("BB")); boost::tuple<string, string> q2(string("AA"), string("CC")); cout << (q < q2) << endl; return 0; }
清單 6 的輸出爲1 1 1
。請注意,如果您不是使用string
或int
,而是使用沒有定義==
、!=
等運算符的用戶定義的隨機類,則會產生編譯錯誤。
斷言是 C/C++ 中的防錯性程序設計的一部分。最常見的用法如下:
assert(<some expression you expect to be true at this point in code>); |
assert
例程僅在調試模式下有效。在發佈模式下,通常使用預處理器宏 ¨CDNDEBUG
來編譯代碼,其效果相當於 assert 不存在。靜態斷言建立在這個基本概念之上,只不過靜態斷言僅在編譯時有效。此外,靜態斷言不生成任何代碼。
例如,假設您在一個整型變量中執行某個位操作,並預期其大小爲 4:這並非在所有操作系統平臺上都是如此(請參見清單 7)。
#include <boost/static_assert.hpp> int main ( ) { BOOST_STATIC_ASSERT(sizeof(int) == 4); // … other code goes here return 0; } |
要使用 BOOST_STATIC_ASSERT
宏,您必須包括 static_assert.hpp 頭文件。不需要諸如 DNDEBUG
等特定於編譯器的選項,並且您不需要向鏈接器提供庫——單憑該頭文件就足夠了。
如果斷言有效,則代碼將順利編譯。但是如果該假設無效,在某些 64 位平臺上就可能是如此,則編譯器將生成錯誤消息並停止。使用 g++-3.4.4 進行編譯時的典型消息如下:
assert.cc: In function `int main()': assert.cc:8: error: incomplete type `boost::STATIC_ASSERTION_FAILURE< false>' used in nested name specifier |
這肯定不是最詳細的錯誤消息,但是它指出了具有錯誤假設的函數和確切行號。
下面是一些典型的現實情景,您應該在其中考慮使用靜態斷言:
- 靜態聲明的數組的邊界檢查
- 驗證原始和用戶定義的變量的大小
- 允許模板類或函數僅使用某些數據類型來進行實例化
您可以在類、函數或命名空間範圍中使用 Boost 靜態斷言;還可以與模板一起使用它們。清單 8 中的示例闡明瞭概念。
#include <iostream> #include <static_assert.hpp> using namespace std; using namespace boost; template<class T> class A { private: T x, y; BOOST_STATIC_ASSERT(numeric_limits<T>::is_signed); public: A(T x1, T y1) : x(x1), y(y1) { } }; int main ( ) { A<unsigned long> a(2, 1); return 0; } |
在清單 8 中,僅當 T
有符號時,模板類 A
才能進行實例化。類 numeric_limits
是標準命名空間的一部分;它檢查基本類型在給定操作系統平臺上的屬性。在無符號(unsigned )的 long
類型的情況下,專用變體 numeric_limits<unsigned int>
的 is_signed
標誌爲 false。當您在類範圍中使用 BOOST_STATIC_ASSERT
時,它是私有的、受保護的還是公開的並不重要。
清單 9 將 BOOST_STATIC_ASSERT
與函數結合在一起使用。該代碼確保在函數 f1
中處理的類型只能是 A
類型或其派生類型。通過使用 Boost 的靜態斷言宏和 is_convertible
例程(在 boost/type_traits/is_convertible.hpp 中定義),此代碼確保不希望的類型不會最終調用此例程。
#include <iostream> #include <static_assert.hpp> #include <boost/type_traits/is_convertible.hpp> using namespace std; using namespace boost; struct A { int a; float b; }; struct B : public A { }; template <typename T> int f1 (T y) { BOOST_STATIC_ASSERT((is_convertible<T, A*>::value)); return 0; } int main ( ) { f1<B*> (new B); return 0; } |
隨機數生成用於各種各樣的計算機應用,例如安全和遊戲。UNIX 系統一般附帶了隨機數生成例程 rand
和 srand
。通常,srand
使用新的種子值來初始化 rand
(請參見清單 10)。
#include <stdlib.h> #include <stdio.h> int main ( ) { srand(time(NULL)); // this introduces randomness for (int i=0; i<10; i++) printf("%d/n", rand()); return 0; } |
rand
例程返回一個介於 0 和 stdlib.h 中定義的 RAND_MAX
之間的數字。要了解 srand
所做的工作,可以在將 srand
例程註釋掉的情況下編譯清單 11。當您這樣做時,您將觀察到 rand
並不真正是隨機的——可執行代碼每次打印同一組值。爲了在代碼中引入隨機性,您可以使用 srand
,此例程使用種子值來初始化 rand
。由於每次調用程序時的時間值是不同的,因此對於不同的調用,代碼打印的值不同。
Boost 隨機數生成器位於 boost/random 文件夾中。此外,爲方便起見,boost/ 目錄中的 random.hpp 頭文件包括了 boost/random 文件夾中的所有其他頭文件。
Boost 隨機接口劃分爲兩個部分:隨機數生成器和隨機數必須位於其中的分佈。本文討論 uniform_int
和 uniform_real random-number
分佈以及 mt19937
隨機數生成器。清單 11 使用了 uniform_int
和 uniform_real
分佈。
清單 11. 將 variate_generator
與 mt19937
引擎和 uniform_int
分佈一起使用
#include <iostream> #include <boost/random.hpp> using namespace std; using namespace boost; int main ( ) { uniform_int<> distribution(1, 100) ; mt19937 engine ; variate_generator<mt19937, uniform_int<> > myrandom (engine, distribution); for (int i=0; i<100; ++i) cout << myrandom() << endl; return 0; } |
此代碼生成介於 1 和 100 之間(包括 1 和 100)的隨機數;用於實現隨機化的基礎引擎是 mt19937
。variate_generator
爲您組合了該引擎和分佈。
清單 12 使用了另一個引擎: kreutzer1986
.
清單 12:組合 uniform_real
分佈和 kreutzer1986
引擎
#include <iostream> #include <boost/random.hpp> using namespace std; using namespace boost; int main ( ) { uniform_real<> distribution(1, 2) ; kreutzer1986 engine ; variate_generator<kreutzer1986, uniform_real<> > myrandom (engine, distribution); for (int i=0; i<100; ++i) cout << myrandom() << endl; return 0; } |
除了 uniform_int
和 uniform_real
分佈以外,Boost 還提供了幾個其他分佈,包括二項式、泊松和正態分佈。
Boost pool
庫引入了可用於實現快速內存分配的工具。正確的內存塊對齊可以得到保證。
根據 Boost 文檔所述,當您分配和釋放許多小型對象時,建議使用池。使用池的另一個不太明顯的優點在於,作爲程序員,您不必擔心內存泄露:內存由 Boost 庫在內部自動進行管理。要使用 pool
庫,您不必在鏈接時提供特定的庫——單憑頭文件就足以完成鏈接了。
有多個接口對 pool
庫可用:
- 池接口——替代
malloc
進行工作的普通接口。要使用此接口,需要包括 boost/pool 文件夾中的 pool.hpp 頭文件。 - 對象池接口——有對象意識的接口,在對象創建和刪除過程中分別相應地調用構造函數和析構函數。還可以使用此接口創建普通對象,而不調用它們的構造函數。接口定義是在位於 boost/pool 目錄中的 object_pool.hpp 頭文件中提供的。清單 13 引入了
pool
和object_pool
接口。請注意以下幾點:pool
接口需要知道每個單獨的元素而不是類型的大小,因爲它是一個malloc
風格的分配程序,不會調用構造函數。pool
接口中的malloc
例程返回void*
。object-pool
接口需要類型信息,因爲要調用構造函數。object-pool
接口中的malloc/construct
例程返回指向類型的指針。malloc
例程不調用構造函數,但是construct
要調用構造函數。- 使用
pool
接口或object-pool
接口來創建的元素的範圍與從中創建它們的池的範圍相同。 - 要從池接口中釋放內存,可以調用
purge_memory
方法。該方法釋放您先前創建的內存塊,並使得從分配程序例程返回的所有指針失效。 - 要釋放各個元素,可以調用
pool
接口中的free
例程。例如,如果t
是使用pool
接口來創建的池,並且m
是從t
分配的指針,則t.free(m)
將把內存返回給t
(將其添加到t
的空閒內存列表)。
清單 13.pool
和object_pool
接口#include <iostream> #include <boost/pool/pool.hpp> #include <boost/pool/object_pool.hpp> using namespace std; using namespace boost; class A { public: A( ) { cout << "Declaring A/n"; } ~A( ) { cout << "Deleting A/n"; } }; int main ( ) { cout << "Init pool.../n"; pool<> p(10 * sizeof(A)); for (int i=0; i<10; ++i) A* a = (A*) p.malloc(); // Always returns sizeof(A) p.purge_memory(); cout << "Init object pool.../n"; object_pool<A> q; for (int i=0; i<10; ++i) A* a = q.construct(); // Calls A's constructor 10 times return 0; }
singleton_pool
接口——與pool
接口幾乎相同,但是用作獨立池。獨立池的底層結構具有爲malloc
、free
等聲明的靜態成員函數,並且構造函數是私有的。獨立池聲明中的第一個參數稱爲標記——它允許存在不同的獨立池集(例如,用於int
的多個池,其中每個池服務於不同的目的)。必須包括 singleton_pool.hpp 頭文件才能使用此接口。請參見清單 14。
清單 14.singleton_pool
接口#include <iostream> #include <boost/pool/singleton_pool.hpp> using namespace std; using namespace boost; struct intpool { }; struct intpool2 { }; typedef boost::singleton_pool<intpool, sizeof(int)> ipool1; typedef boost::singleton_pool<intpool2, sizeof(int)> ipool2; int main ( ) { cout << "Init singleton pool.../n"; for (int i=0; i<10; ++i) { int* q1 = (int*) ipool1::malloc(); int* q2 = (int*) ipool2::malloc(); } ipool1::purge_memory(); ipool2::purge_memory(); return 0; }
pool_alloc
接口——通常與 STL 容器結合在一起使用。請考慮以下代碼片段:#include <boost/pool/pool_alloc.hpp> std::vector<int, boost::pool_allocator<int> > v; std::list<double, boost::fast_pool_allocator<double> > L;
存在兩個分配程序:pool_allocator
和fast_pool_allocator
。第一個分配程序是通用分配,可以滿足針對任何數量的連續內存塊的請求。fast_pool_allocator
最適合於一次請求單個(通常較大)塊,但是也適用於通用分配,不過具有一些性能缺點。
命令行處理是另一個難點,開發人員通常不會採用結構化的方式來解決。其結果是從頭到尾維護開銷。Boost program_options
庫提供了簡化命令行處理的例程和數據結構。
清單 15 詳細描述了 boost::program_options
的使用。這是建議在您的代碼中使用的標準模板。
清單 15. 使用 boost::program_options
#include <string> #include <iostream> #include <boost/program_options.hpp> using namespace std; int main (int ac, char* av[]) { boost::program_options::options_description options("command line options"); options.add_options() ("help", "Use -h or --help to list all arguments") ("file", boost::program_options::value<string>(), "Provide input file name"); boost::program_options::variables_map vmap; boost::program_options::store( boost::program_options::parse_command_line(ac, av, options), vmap); boost::program_options::notify(vmap); if (vmap.count("help")) { cout << options << endl; } return 0; } |
您必須包括 program_options.hpp 頭文件。清單 15 的工作方式如下:
options_description
類聲明所有的有效命令行選項。- 使用方法
add_options
,您可以註冊命令和跟在命令後面的參數類型。在此例中,help
選項不需要任何參數,但是file
選項需要一個字符串參數。 variables_map
類在運行時存儲命令行選項及其參數。- Boost 的
parse_command_line
例程解析argc
和argv
參數。store
和notify
方法幫助存儲vmap
對象中的數據。 - 當您檢查
help
是否爲程序的恰當命令行選項(這是vmap.count("help")
所做的工作)時,options
對象將被轉儲到cout
。這意味着運算符<<
是爲options_description
類定義的。
下面是來自清單 15 的輸出:
[user@/home/user1] ./a.out --help command line options: --help Use -h or --help to list all arguments --file arg Provide input file name |
當您遇到其他選項時,可以採取進一步的操作。例如,下面的代碼片段經過了修改,以打印您輸入的文件名:
… if (vmap.count("file")) { cout << "Setting input file to " << vmap["file"].as<string>() << "./n"; } else { cout << "No file specified/n"; } … |
請注意,variable_map
類在許多方面與哈希表非常相似。例如,要檢索 file 參數,您可以調用 vmap["file"]
。
命令行處理通常同時需要同一個命令選項的短名稱和長名稱。此外,您通常必須多次使用某個選項,以便收集該選項的所有參數。例如,您可能希望使用 ¨Ch
和 ¨Chelp
來打印可用的命令。清單 16 演示了這些功能。
#include <string> #include <iostream> #include <boost/program_options.hpp> using namespace std; int main (int ac, char* av[]) { boost::program_options::options_description options("command line options"); options.add_options() ("help,h", "Use -h or --help to list all arguments") ("file", boost::program_options::value<vector<string> >( ), "Provide input file name"); boost::program_options::variables_map vmap; boost::program_options::store( boost::program_options::parse_command_line(ac, av, options), vmap); boost::program_options::notify(vmap); if (vmap.count("help")) { cout << options << endl; } if (vmap.count("file")) { vector<string> ifiles(vmap["file"].as< vector<string> > ()); vector<string>::iterator vI; cout << "Number of input files: " << ifiles.size() << endl; cout << "Input file list: " << endl; for(vI = ifiles.begin(); vI != ifiles.end(); ++vI) cout << "/t" << *vI << endl; } else { cout << "No file specified/n"; } return 0; } |
在使用 add_options
來添加命令選項時,較長和較短的選項之間使用逗號進行分隔。請注意,較長的選項 (help
) 必須在較短的選項 (h
) 之前,代碼才能正常工作。與使用單個字符串不同,file
選項現在是使用一個字符串向量來定義的。如果指定了 ¨Cfile
選項多次,則會將在所有指定中收集到的命令選項參數存儲在關聯的 vector<string>
中。下面是使用不同的參數來多次指定 ¨Ch
和 ¨Cfile
所獲得的輸出:
[user@/home/user1] ./a.out -h command line options: -h [ --help ] Use -h or --help to list all arguments --file arg Provide input file name No file specified [user@/home/user1] ./a.out --file abc --file pqr Number of input files: 2 Input file list: abc pqr |
帶輸入參數但是不帶命令行選項來調用某個程序是非常普遍的。您預期參數和命令行選項之間自動存在某種神奇關聯。這種行爲由 boost::program_options
提供支持。
請考慮清單 17。第一個參數轉換爲 --file=<first parameter>
,第二個參數轉換爲 --do-file=<second parameter>
。
#include <string> #include <iostream> #include <boost/program_options.hpp> using namespace std; int main (int ac, char* av[]) { boost::program_options::options_description options("command line options"); options.add_options() ("help,h", "Use -h or --help to list all arguments") ("file", boost::program_options::value<string>(), "Provide input file name") ("do-file", boost::program_options::value<string>(), "Specify commands file"); boost::program_options::variables_map vmap; boost::program_options::positional_options_description poptd; poptd.add("file", 1); poptd.add("do-file", 2); boost::program_options::store( boost::program_options::command_line_parser(ac, av). options(options).positional(poptd).run(), vmap); boost::program_options::notify(vmap); if (vmap.count("file")) { cout << "file: " << vmap["file"].as<string> ( ) << endl; } if (vmap.count("do-file")) { cout << "do-file: " << vmap["do-file"].as<string> ( ) << endl; } return 0; } |
下面是輸出內容:
[user@/home/user1] ./a.out file1 dofile1 file: file1 do-file: dofile1 |
清單 15 中使用的某些 API 在清單 17 中已發生更改。清單 17 引入了新的類 positional_options_description
。該類的 add
方法(add("command option", N)
)將位置 N 處的輸入參數與命令行選項 "command option"
相關聯。因此,./a.out file1
在內部解析爲 ./a.out ¨Cfile=file1
。另一個區別在於調用 program_options::store
方法的方式。與使用 parse_command_line
例程不同,Boost 庫要求您將 command_line_parser
例程與 store
方法結合在一起使用。
請注意,仍然可以使用 ¨Cfile
和 ¨Cdo-file
選項來調用該程序。最後,若要將所有的輸入參數與同一個命令行選項相關聯,您需要使用值 -1 將該命令行選項添加到 positional_options_description
對象。下面是代碼:
… boost::program_options::positional_options_description poptd; poptd.add("file", -1); ... |
本文提供了 Boost 庫中較爲有用的一些實用工具類的概述。只要使用得當,它們可以爲您省去大量的代碼維護開銷。鑑於所討論的概念的範圍和廣度,論及每個實用工具類的每個功能是不切實際的。參考資料部分提供了進一步的閱讀參考。
學習
- 您可以參閱本文在 developerWorks 全球站點上的 英文原文 。
- 閱讀 tuple user guide。
- 請參閱 Static Assertion Reference。
- 獲取有關隨機數生成器的更多信息。要了解隨機數生成器的數學基礎,請參閱圖書 Numerical Recipes in C。
- 閱讀 Boost pool library documentation。
- 從 program-options user guide 中獲取更多信息。
- AIX and UNIX 專區:developerWorks 的“AIX and UNIX 專區”提供了大量與 AIX 系統管理的所有方面相關的信息,您可以利用它們來擴展自己的 UNIX 技能。
- AIX and UNIX 新手入門:訪問“AIX and UNIX 新手入門”頁面可瞭解更多關於 AIX 和 UNIX 的內容。
- AIX and UNIX 專題彙總:AIX and UNIX 專區已經爲您推出了很多的技術專題,爲您總結了很多熱門的知識點。我們在後面還會繼續推出很多相關的熱門專題給您,爲了方便您的訪問,我們在這裏爲您把本專區的所有專題進行彙總,讓您更方便的找到您需要的內容。
- developerWorks 技術事件和網絡廣播:瞭解最新的 developerWorks 技術事件和網絡廣播。
- AIX:訪問此 AIX 相關技術信息的協作環境。
- Podcast:收聽 Podcast 並與 IBM 技術專家保持同步。
獲得產品和技術
- 下載 Boost 實用工具類。
- IBM 試用軟件:從 developerWorks 可直接下載這些試用軟件,您可以利用它們開發您的下一個項目。
討論
- 參與“AIX and UNIX”論壇: