歡迎您光臨本站 註冊首頁

Boost 庫中的實用工具類使用入門

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  
實用工具類開發和維護會耗費程序員的時間。Boost 通過提供幾個高質量的庫,從而簡化了此過程,您可以將這些庫輕鬆集成到現有的代碼庫中。本文簡單概述一些較流行的 Boost 實用工具類,並幫助您了解如何將它們投入使用。

實用工具類(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 來進行了編譯。

boost::tuple 類

有時,您希望 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 的元組並列印內容。


清單 1. 創建 Boost 元組並列印內容
                  #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,而是使用沒有定義 ==、!= 等運算符的用戶定義的隨機類,則會產生編譯錯誤。




Boost 靜態斷言

斷言是 C/C++ 中的防錯性程序設計的一部分。最常見的用法如下:

assert(<some expression you expect to be true at this point in code>);  		    

assert 常式僅在調試模式下有效。在發布模式下,通常使用預處理器宏 ¨CDNDEBUG 來編譯代碼,其效果相當於 assert 不存在。靜態斷言建立在這個基本概念之上,只不過靜態斷言僅在編譯時有效。此外,靜態斷言不生成任何代碼。

例如,假設您在一個整型變數中執行某個位操作,並預期其大小為 4:這並非在所有操作系統平台上都是如此(請參見清單 7)。


清單 7. 使用 Boost 靜態斷言來驗證變數的大小
                  #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 靜態斷言的行為

您可以在類、函數或命名空間範圍中使用 Boost 靜態斷言;還可以與模板一起使用它們。清單 8 中的示例闡明了概念。


清單 8. 使用 Boost 靜態斷言來限制類實例化
                  #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 中定義),此代碼確保不希望的類型不會最終調用此常式。


清單 9. 將函數限制為僅處理特定的數據類型
                  #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;    }    





使用 Boost 庫生成隨機數

隨機數生成用於各種各樣的計算機應用,例如安全和遊戲。UNIX 系統一般附帶了隨機數生成常式 rand 和 srand。通常,srand 使用新的種子值來初始化 rand(請參見清單 10)。


清單 10. 用於在傳統 UNIX 中生成隨機數的代碼
                  #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 隨機數生成器位於 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 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 簡介

命令行處理是另一個難點,開發人員通常不會採用結構化的方式來解決。其結果是從頭到尾維護開銷。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 的工作方式如下:

  1. options_description 類聲明所有的有效命令行選項。
  2. 使用方法 add_options,您可以註冊命令和跟在命令後面的參數類型。在此例中,help 選項不需要任何參數,但是 file 選項需要一個字元串參數。
  3. variables_map 類在運行時存儲命令行選項及其參數。
  4. Boost 的 parse_command_line 常式解析 argc 和 argv 參數。store 和 notify 方法幫助存儲 vmap 對象中的數據。
  5. 當您檢查 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 演示了這些功能。


清單 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>。


清單 17. 將位置參數與命令行選項相關聯
                  #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);  ...  

(責任編輯:A6)



[火星人 ] Boost 庫中的實用工具類使用入門已經有814次圍觀

http://coctec.com/docs/linux/show-post-69138.html