boost庫中的 program_options


1、閱讀rviz中的源碼時在rviz/visualizer_app.cpp中遇到如下代碼:

  

po::options_description options;
    options.add_options()
      ("help,h", "Produce this help message")
      ("splash-screen,s", po::value<std::string>(), "A custom splash-screen image to display")
      ("help-file", po::value<std::string>(), "A custom html file to show as the help screen")
      ("display-config,d", po::value<std::string>(), "A display config file (.rviz) to load")
      ("fixed-frame,f", po::value<std::string>(), "Set the fixed frame")
      ("ogre-log,l", "Enable the Ogre.log file (output in cwd) and console output.")
      ("in-mc-wrapper", "Signal that this is running inside a master-chooser wrapper")
      ("opengl", po::value<int>(), "Force OpenGL version (use '--opengl 210' for OpenGL 2.1 compatibility mode)")
      ("disable-anti-aliasing", "Prevent rviz from trying to use anti-aliasing when rendering.")
      ("no-stereo", "Disable the use of stereo rendering.")
      ("verbose,v", "Enable debug visualizations")
      ("log-level-debug", "Sets the ROS logger level to debug.");

  其中po是命名空間:namespace po = boost::program_options;

  看到options對象的成員函數add_options()后面接了一連串的括號,表示很不理解,在網上查了下相關資料,整理下自己的學習所得,順便在這里記錄下來。

  實際上上面使用了boost庫中的program_options,即程序參數項,它是由一系列的(name,value)鍵值對構成,program_options允許程序開發者通過命令行和配置文件獲取這些參數項。

  

  program_options的使用主要通過下面三個組件完成:
          組件名                   作用
    options_description(選項描述器)             描述當前的程序定義了哪些選項
    parse_command_line(選項分析器)          解析由命令行輸入的參數
    variables_map(選項存儲器)                容器,用於存儲解析后的選項

  使用時添加頭文件

   #include <boost/program_options.hpp>

  可以把命名空間簡化:
   namespace po = boost::program_options;

 

program_options代碼流程

  • 構造option_description對象和variables_map對象
  • add_options()->向option_description對象添加選項
  • parse_command_line()->將命令行輸入的參數解析出來
  • store()->將解析出的選項存儲至variables_map中
  • notify()->通知variables_map去更新所有的外部變量
  • count()->檢測某個選項是否被輸入
  • operator[]->取出選項的值

 

  program_options對象的成員函數add_options()后面可以加無限多的()是因為此函數返回值類型是options_description_easy_init,函數申明如下:


  
  
  
          
options_description_easy_init add_options();
  options_description_easy_init的類型為:
 
/** Class which provides convenient creation syntax to option_description. 
     */        
    class BOOST_PROGRAM_OPTIONS_DECL options_description_easy_init {
    public:
        options_description_easy_init(options_description* owner);

        options_description_easy_init&
        operator()(const char* name,
                   const char* description);

        options_description_easy_init&
        operator()(const char* name,
                   const value_semantic* s);
        
        options_description_easy_init&
        operator()(const char* name,
                   const value_semantic* s,
                   const char* description);
       
    private:
        options_description* owner;
    };

  顯然,類重載了()符號。

  使用program_options的好處:

    1、使用更容易。定義參數處理的語法簡單,庫自身很小,比如轉換參數值到指定的類型和保存參數值到變量的事情都是自動處理。

 
        

    2、錯誤報告更友好。可報告錯誤的命令行參數,庫能自動生成使用幫助,避免手工更新使用幫助導致的不一致。

 
        

    3、參數能從不同的地方讀取,當命令行參數不能滿足要求,需要改用配置文件或環境變量,這些功能都能支持,代碼改動很小。

使用示例:

 
        
#include <iostream>
#include <boost/program_options.hpp>

using namespace std;
using namespace boost;
using namespace boost::program_options;

int main(int argc,char* argv[])
{
    string config = "";
    string data_type = "";
    string version = "";
    try
    {
        options_description desc("Allowed options");
        desc.add_options()
                ("help,h", "produce help message")
                ("config,c", value<string>()->default_value(""), "set config")
                ("data_type,t", value<string>()->default_value(""), "set data type")
                ("version,v", value<string>()->default_value(""), "set version");

        variables_map vm;
        store(parse_command_line(argc, argv, desc), vm);
        notify(vm);

        if (vm.count("help"))
        {
            cerr << desc << "\n";
            return 1;
        }
        if (vm.count("config"))
        {
            if (vm["config"].as<string>().empty())
            {
                cerr<<"config is empty!!!"<<endl;
                return -1;
            }
            else
            {
                config = vm["config"].as<string>();
                cerr<<"config is: "<<config<<endl;
            }
        }
        if (vm.count("data_type"))
        {
            if (vm["data_type"].as<string>().empty())
            {
                cerr<<"data_type is empty!!!"<<endl;
                return -1;
            }
            else
            {
                data_type = vm["data_type"].as<string>();
                //cerr<<"data_type is: "<<data_type<<endl;
            }
        }
        if (vm.count("version"))
        {
            if (vm["version"].as<string>().empty())
            {
                //cerr<<"version is empty!!!"<<endl;
            }
            else
            {
                version = vm["version"].as<string>();
                //cerr<<"version is: "<<version<<endl;
            }
        }
    }
    catch (std::exception& e)
    {
        cerr<<"error: "+string(e.what())<<endl;
        return -1;
    }
    catch(...)
    {
        cerr<<"Exception of unknown type!"<<endl;
        return -1;
    }

    search_business::BaseBusiness bp(data_type);
    if (false == bp.init(config, version))
    {
        cerr << "init error" << endl;
        return 1;
    }

    if (bp.work() != 0)
    {
        cerr << "work error" << endl;
        return 2;
    }
    return  0;
}

 

 

  以下內容來源於以下兩個博客

  https://blog.csdn.net/cchd0001/article/details/43967451

  https://blog.csdn.net/morning_color/article/details/50241987

 

定義並添加命令說明

int opt;
/* 類 options_description 是存儲選項詳細信息的類 */
options_description parser("name_your_like");
/*add_option() 支持() 操作. 多次調用add_options() 的效果等同於將所有的選項使用() 並列聲明*/
parser.add_options()
         /*無參數的選項格式 : "命令名" , "命令說明" */
         ("help", "produce help message")
         /* 有參賽的選項格式 : 
          "命令名" , "參數說明" , "命令說明" */ 
         ("set-float" ,value<float>(), "set a float parameter")
         /* 參數對應已有數據(特定地址) , 並且有初始值 : */ 
         ("optimization", value<int>(&opt)->default_value(10), 
             "optimization level")
         /* 支持短選項 ( --include-path 和 -I 等效 ) ,
            多次調用存在依次vector中 */
         ("include-path,I", value< vector<string> >(), 
             "include path")
        ;

解析命令行 , 存儲在variables_map 類的變量中

/* variables_map 類是用來存儲具體傳入參數數據的類 , 和map很相似. 使用as接口來傳出數據 */
variables_map vm;
/* ac 就是參數個數 , av 就是參數列表指針 */
/* int ac , const char ** av */
/* parse_command_line 接口不支持猜測選項 */
store(parse_command_line(ac, av, desc), vm);
notify(vm);    

注意: 如果傳入沒有定義的選項, 會拋出異常!

判斷命令是否觸發 , 以 help 為例

/* variables_map 使用 count 接口來判斷具體選項是否觸發 */
if (vm.count("help")) {
   /*如果觸發就輸出命令說明 , option_description 類重載了 << 行為, 將格式化的輸出
   命令說明 */
    cout << desc << "\n";
    return 1;
}

取出傳入的參數 , 以上面的float參數為例

首先, 如果已經指定了一個接受數據 [ 比如 : ("optimization", value<int>(&opt)->default_value(10), "optimization level") ]的話,就可以直接使用它.

if (vm.count("set-float")) {
    cout << "set-float "<<
    /*map直接使用鍵值當index , 然后使用as< 具體類型> 接口來取出參數*/ 
    vm["set-float"].as<float>() << ".\n";
} else {
    cout << "float not set\n";
}

定義默認參數.

上面的例子中 ,如下操作:

    $ a.outset-float=0.131415926
    $ set-float 0.131415926


然而假設我們想要以下面的命令實現上面的效果:

    $ a.out 0.131415926
    $ set-float 0.131415926 

 

就需要設置set-float 為默認的參數.

int opt;
options_description parser("name_your_like");
parser.add_options()
         ("help", "produce help message")
         ("set-float" ,value<float>(), "set a float parameter")
         ("optimization", value<int>(&opt)->default_value(10),
             "optimization level")
         ("include-path,I", value< vector<string> >(),
             "include path")
        ;
/* 定義一個可以猜測的命令 positional_options_description */
positional_options_description p;
/* 指定猜測的時候參數的位置, -1 標識沒有任何限制 */
p.add("set-float", -1);

variables_map vm;
/* 使用可以猜測的命令行解析接口來解析 */
store(command_line_parser(ac, av).options(desc).positional(p).run(), vm);
notify(vm);  

這樣就支持了默認選項是 –set-float

 

對配置文件的支持

 /*將文件讀入流*/
 ifstream ifs("file_name");
 /* 調用解析文件接口 */
 store(parse_config_file(ifs, desc), vm);
 notify(vm); 

 下面的代碼是boost::program_options的一個簡單的用法示例.
該示例中指定了兩個選項,分別是–help和–filename.

//linux系統下,編譯選項需加上 -lboost_program_options

#include <iostream>
#include <string>
#include <boost/program_options.hpp>
namespace  bpo = boost::program_options;

int main(int argc, char const *argv[])
{
    //步驟一: 構造選項描述器和選項存儲器
    //選項描述器,其參數為該描述器的名字
    bpo::options_description opts("all options"); 
    //選項存儲器,繼承自map容器
    bpo::variables_map vm;

    //步驟二: 為選項描述器增加選項
    //其參數依次為: key, value的類型,該選項的描述
    opts.add_options()  
    ("filename", bpo::value<std::string>(), "the file name which want to be found")
    ("help", "this is a program to find a specified file");

    //步驟三: 先對命令行輸入的參數做解析,而后將其存入選項存儲器
    //如果輸入了未定義的選項,程序會拋出異常,所以對解析代碼要用try-catch塊包圍
    try{
        //parse_command_line()對輸入的選項做解析
        //store()將解析后的結果存入選項存儲器
        bpo::store(bpo::parse_command_line(argc, argv, opts), vm);
    }
    catch(...){
        std::cout << "輸入的參數中存在未定義的選項!\n";
        return 0;
    }

    //步驟四: 參數解析完畢,處理實際信息
    //count()檢測該選項是否被輸入
    if(vm.count("help") ){//若參數中有help選項
        //options_description對象支持流輸出, 會自動打印所有的選項信息
        std::cout << opts << std::endl;   
    }
    if(vm.count("filename") ){
        //variables_map(選項存儲器)是std::map的派生類,可以像關聯容器一樣使用,
        //通過operator[]來取出其中的元素.但其內部的元素類型value_type是boost::any,
        //用來存儲不確定類型的參數值,必須通過模板成員函數as<type>()做類型轉換后,
        //才能獲取其具體值.
        std::cout << "find " << vm["filename"].as<std::string>() << std::endl;
    }
    if(vm.empty() ){
        std::cout << "no options found \n";
    }
    return 0;
}

在編譯后(假設在linux系統下其編譯后的可執行文件為a.out)
輸入

    ./a.out --help

則其輸出為:

    all options:
    –filename arg the file name which want to be found
    –help this is a program to find a specified file

輸入

    ./a.out --filename=program_test

則其輸出為

    find program_test

若不指定選項,即輸入

    ./a.out

則輸出為

    no options found  

示例二

下面的這個示例主要用來說明外部變量,參數默認值以及一個選項對應多個值的情況
這段代碼主要有四個選項:

/*
–apple : 蘋果的數量
–orange:橘子的數量
–address:水果的生產地,可指定多個生產地
–help: 打印幫助信息
*/

///////////////////////////////////////////
//計算橘子和蘋果的總數量,可以指定多個生產地    //
//編譯選項加上 -lboost_program_options     //
///////////////////////////////////////////
#include <iostream>  
#include <vector>  
#include <string>  
#include <boost/program_options.hpp>  
namespace bpo = boost::program_options;  

int main(int argc, char const *argv[])  
{  
    //外部變量,用於保存獲取的參數值  
    int apple_num = 0, orange_num = 0;  
    std::vector<std::string> addr;  
    bpo::options_description opt("all options");  

    opt.add_options()  
    //指定該參數的默認值 
    // "apple,a" : 指定選項的全寫形式為 --apple, 簡寫形式為 -a
    //value<type>(ptr) : ptr為該選項對應的外部變量的地址, 當該選項被解析后, 
    //可通過下面的notify()函數將選項的值賦給該外部變量,該變量的值會自動更新
    //defaut_value(num) : num為該選項的默認值, 若命令行中未輸入該選項, 則該選項的值取為num
    ("apple,a", bpo::value<int>(&apple_num)->default_value(10), "蘋果的數量")  
    ("orange,o", bpo::value<int>(&orange_num)->default_value(20), "橘子的數量")  
    //該參數的實際類型為vector,所以命令行中輸入的值可以是多個,
    //multitoken()的作用就是告訴編譯器,該選項可接受多個值  
    ("address", bpo::value<std::vector<std::string> >()->multitoken(), "生產地")  
    ("help", "計算蘋果和橘子的總數量");  

    bpo::variables_map vm;  

    try{  
        bpo::store(parse_command_line(argc, argv, opt), vm);  
    }  
    catch(...){  
        std::cout << "輸入的參數中存在未定義的選項!\n";  
        return 0;  
    }  
    //參數解析完成后,通知variables_map去更新所有的外部變量
    //這句話執行后, 會使得apple_num和orange_num的值自動更新為選項指定的值   
    bpo::notify(vm);  

    if(vm.count("help") ){  
        std::cout << opt << std::endl;  
        return 0;  
    }  
    if(vm.count("address") ){  
        std::cout << "生產地為:";  
        //遍歷選項值  
        for(auto& str : vm["address"].as<std::vector<std::string> >() )  
            std::cout << str << " ";  
        std::cout << std::endl;   
    }  
    std::cout << "蘋果的數量:" << apple_num << std::endl;  
    std::cout << "橘子的數量:" << orange_num << std::endl;  
    std::cout << "總數量數量:" << orange_num + apple_num << std::endl;  
    return 0;  
}  

輸入

    ./a.out --help

輸出

    all options:
    -a [ –apple ] arg (=10) 蘋果的數量
    -o [ –orange ] arg (=20) 橘子的數量
    –address arg 生產地
    –help 計算蘋果和橘子的總數量

指定蘋果和橘子的數量:

    ./a.out --apple=8 –orange=20

其輸出為:

    蘋果的數量:8
    橘子的數量:20
    總數量數量:28

僅指定橘子的數量,不指定蘋果的數量:

    ./a.out --orange=20

其輸出為:

    蘋果的數量:10
    橘子的數量:20
    總數量數量:30

可以看到,由於沒有輸入蘋果的數量,所以輸出的蘋果的數量為我們指定的默認值

指定一個生產地:

    ./a.out --apple=8 --orange=20 --address=山東

輸出:

    生產地為:山東
    蘋果的數量:8
    橘子的數量:20
    總數量數量:28

指定多個生產地:

    ./a.out --apple=8 --orange=20 --address=山東 陝西

輸出為

    生產地為:山東 陝西
    蘋果的數量:8
    橘子的數量:20
    總數量數量:28

簡寫形式的輸入:

    ./a.out -a 8 -o 20 --address=山東

輸出:

    生產地為:山東
    蘋果的數量:8
    橘子的數量:20
    總數量數量:28

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM