![]() |
Home | Libraries | People | FAQ | More |
在上一節「指南」中,我們看到了幾個用例。這一節我們將介紹整個庫的設計,包括主要的組件以及它們的作用。
本庫有三個主要組件:
選項描述組件,描述允許的選項及這些選項的值。
分析器組件,用於從數據輸入源中查找選項名字及值,並返回它們。
存儲器組件,提供讀取選項值的接口,同時負責將分析器所返回的、以字符串方式表示的值轉換為需要的C++類型。
說得具體些,options_description 類即來自於選項描述組件,parse_command_line 函數則來自於分析器組件,而
variables_map 類則來自於存儲器組件。
在上一節,我們已經學習了如何在
main 函數中使用這些組件來分析命令行以及配置文件。在進入到這些組件的細節之前,還要注意 main 以外的一些東西。
在 main
以外,存儲器組件是最重要的。它提供了一個類,用於保存所有選項值,並且這個類可以自由地從你的程序傳遞到任何需要訪問選項的模塊。而其它組件只能在進行
分析處理的地方使用。但是,對於獨立的程序模塊來說,描述它們自己的選項並傳遞給主模塊也是很有意義的。當然,這種做法只有在選項數量太多導致在一個地方
聲明它們很麻煩時才會顯得重要。
選項描述組件有三個主要的類:option_description, value_semantic 和 options_description。前兩個一起用於描述單個選項。option_description 類包含選項的名字、描述以及一個指向 value_semantic 的指針,後者知道選項值的類型且可以分析該值,或賦予缺省值。options_description 類則是包含多個 option_description 實例的容器。
幾乎每個庫中的類都是以一種常規的方法來創建的:即使用構造函數來創建一個新的選項,然後調用 options_description 的 add 方法。但是,如果有20或30個選項要聲明,這就顯得很冗長了。這導致了你已經見過的另一種創建語法:
options_description desc;
desc.add_options()
("help", "produce help")
("optimization", value<int>()->default_value(10), "optimization level")
;
對 value 函數的調用創建了一個派生自 value_semantic 類的子類:typed_value 的實例。這個類包含了分析一個特定類型的值的代碼,以及一組可供用戶調用以指定額外信息的方法(實質上它模擬了構造函數的命名參數)。在這樣一個對像之上調用其
operator(),將通過 add_options
前轉參數至 option_description 類的構造函數,並加入新的實例。
請注意,除了
value, 庫還提供了 bool_switch
函數, 用戶可以編寫自己的函數來返回 value_semantic 的其它子類,以實現不同的行為。在本節的剩餘部分,我們只討論 value 函數。
一個選項的信息可分為語法和語義兩部分。語法信息包含了選項的名字和用於指定其值的記號數量。分析器使用該信息將記號組合為(名字,值)對,其中的值是一個字符串向量
(std::vector<std::string>). 語義層面則負責將選項的值轉換為更可用的C++類型。
這種分離是庫設計的重要部分。分析器只用於語法層面,這樣就沒有了使用過於複雜的結構的自由。例如,很難分析以下語法:
calc --expression=1 + 2/3
因為無法在不知道它是一個C表達式的前提下分析
1 + 2/3
在得到用戶的一點幫助後任務會變得容易,語法也更清晰:
calc --expression="1 + 2/3"
語法信息由
boost::program_options::options_description 類和
boost::program_options::value_semantic 類的某些方法提供,包括:
選項名,用於在程序中標識選項,
選項的描述,用於顯示給用戶,
允許的組成選項值的源記號數量,在分析時使用。
考慮以下例子:
options_description desc;
desc.add_options()
("help", "produce help message")
("compression", value<string>(), "compression level")
("verbose", value<string>()->zero_token(), "verbosity level")
("email", value<string>()->multitoken(), "email to send to")
;
在第一個參數中,我們只指定了選項的名字和描述。因此在被分析的源中不能指定選項值。對於第二個選項,用戶必須用單個記號指定一個選項值。對於第三 個選項,用戶可以提供單個記號表示選項值,也可以沒有記號。最後一個選項,選項值可以跨越多個記號。例如,如下命令行是可用的:
test --help --compression 10 --verbose --email beadle@mars beadle2@mars
有時選項的描述會很長,例如有幾個選項值需要分開描述時。下面我們說明一下你可以使用的一些簡單的格式化機制。
描述字符串有一個或多個段,以換行符('\n')分隔。在輸出一個選項時,程序庫將計算選項描述的縮入量。每一段被作為帶縮入的單獨一行輸出。如果一段字符不能在一行內輸出,則被分割為多行(各行有相同的縮入量)。
你可以在段的開始插入空格,這樣在該段的第一行就會有額外的縮入。例如:
options.add_options()
("help", " A long help msg a long help msg a long help msg a long help
msg a long help msg a long help msg a long help msg a long help msg ")
;
在第一行指定了四個空格的縮入。輸出如下:
--help A long help msg a long
help msg a long help msg
a long help msg a long
help msg a long help msg
a long help msg a long
help msg
在自動換行時,你可能想要額外的縮入。可以通過在指定地方插入製表符('\t')達到效果。例如:
options.add_options()
("well_formated", "As you can see this is a very well formatted
option description.\n"
"You can do this for example:\n\n"
"Values:\n"
" Value1: \tdoes this and that, bla bla bla bla
bla bla bla bla bla bla bla bla bla bla bla\n"
" Value2: \tdoes something else, bla bla bla bla
bla bla bla bla bla bla bla bla bla bla bla\n\n"
" This paragraph has a first line indent only,
bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla");
將輸出:
--well_formated As you can see this is a
very well formatted
option description.
You can do this for
example:
Values:
Value1: does this and
that, bla bla
bla bla bla bla
bla bla bla bla
bla bla bla bla
bla
Value2: does something
else, bla bla
bla bla bla bla
bla bla bla bla
bla bla bla bla
bla
This paragraph has a
first line indent only,
bla bla bla bla bla bla
bla bla bla bla bla bla
bla bla bla
製表符在輸出前會被刪除。每段只允許一個製表符,否則將拋出 program_options::error 異常。最後,如果製表符不是在段中的第一行或者第一行最後的可能位置上,則將被忽略。
語義信息完全由
boost::program_options::value_semantic 類提供。例如:
options_description desc;
desc.add_options()
("compression", value<int>()->default_value(10), "compression level")
("email", value< vector<string> >()
->composing()->notifier(&your_function), "email")
;
這個聲明為第一個選項指定了缺省值10,而第二個選項則可以多次出現且所有實例會被合併,然後進行分析,程序庫會調用函數 &your_function, 把
"email" 選項的值作為參數傳入。
我們把選項定義為 (name, value) 對,這很簡單也有用,但是在命令行的一種特列情形下,會有問題。命令行可以包含 positional option位置選項, 該選項不需指定名字,例如:
archiver --compression=9 /etc/passwd
在此,"/etc/passwd" 並沒有選項名。
一種解決方案是讓用戶自己取出位置選項並處理。但是我們有更好的辦法 -- 提供一個方法自動給位置選項賦上一個名字,即上述命令行可以解釋為:
archiver --compression=9 --input-file=/etc/passwd
positional_options_description 類允許命令行分析器自動賦上這個名字。該類指定了可以有多少個位置選項,以及對每個選項指定名字。例如:
positional_options_description pd; pd.add("input-file", 1);
指定只有一個位置選項,名字為 "input-file".
可以指定一定數量的,或者所有的位置選項給予相同名字。
positional_options_description pd;
pd.add("output-file", 2).add("input-file", -1);
上例中,頭兩個位置選項與名字 "output-file" 關聯,其餘則與名字 "input-file" 關聯。
![]() |
警告 |
|---|---|
|
|
分析器組件將輸入源分解為 (name, value) 對。每個分析器查找可能的選項,參考選項描述以確定選項是否可知及如何確定它的值。在最簡單的情況下,選項名是顯式給出的,這允許程序庫確定選項是否可知的。如果是可知的,則
value_semantic 實例決定如何得到它的值。(如果不是可知的,則拋出異常)。一般的情況是,用戶顯式指定了該值,或者用戶沒有指定值,但該選項的存在暗示了某個值(如 true)。因此,分析器按需要檢查選項值並返回新的 (name, value) 對。
調用分析器時,你要調用一個函數,傳入選項描述及命令行或配置文件或其它東西。分析的結果被作為 parsed_options
類的一個實例返回。典型地,這個對象被直接傳遞給存儲器組件。但是,也可以直接使用它,或者執行另外的處理。
以上模型有三個例外 -- 都與命令行的傳統用法相關。它們需要選項描述組件的支持,增加的複雜度是可接受的。
在命令行中指定的名字可能與選項名不同 -- 通常會為一個較長的名字提供一個 "短選項名"。同樣常見的是在命令行中使用縮寫名。
有時候需要用多個記號來指定選項值。例如,選項 "--email-recipient" 可能會後跟多個 emails, 每個都是一個單獨的命令行記號。這種行為是被支持的,但是它可能導致分析歧義,缺省是不激活的。
命令行可能包含位置選項 -- 即沒有名字的選項。命令行分析器提供了一種機制來猜測這些選項的名字,正如我們在指南中看到的那樣。
存儲器組件負責:
將選項的最終值保存在一個特定類或普通變量中
處理不同源的優先級
以選項的最終值調用用戶指定的 notify 函數
我們來看看以下例子:
variables_map vm;
store(parse_command_line(argc, argv, desc), vm);
store(parse_config_file("example.cfg", desc), vm);
notify(vm);
variables_map 類用於保存選項值。對 store 函數的兩次調用將命令行和配置文件中找到的選項值相加。最後一個對 notify 函數的調用則運行用戶指定的通知函數,按照需要將選項值保存到普通變量中。
優先級的處理很簡單: store
函數不會改變已賦值的選項的值。在以上例子,對於在命令行有指定值的選項,將在配置文件中忽略。
![]() |
警告 |
|---|---|
|
在保存了所有分析值後,不要忘記調用 |
Environment variables環境變量 是一種字符串變量,它可以在所有程序中通過 C 運行時庫中的 getenv 函數取得。操作系統允許為給定用戶設置初始值,也可以在命令行上修改該值。例如,在 Windows 上你可以用
autoexec.bat 文件或(在較新版本中)
Control Panel/System/Advanced/Environment Variables
對話框,而在 Unix 上則是 /etc/profile,
~/.profile 和 ~/.bash_profile
文件。因為環境變量可以對整個系統設置,它們特別適用於對所有程序有效的選項。
環境變量可通過
parse_environment 函數來分析。該函數有幾個重載版本。第一個參數都是一個 options_description
實例,而第二個參數則指定哪個要處理以及對應於哪個選項名。描述第二個參數時我們需要考慮環境變量的命名習慣。
如果你有一個選項是由環境變量指定的,你需要起一個變量名。為避免名字衝突,我們建議你為環境變量加一個足夠獨特的前綴。另外,選項名通常用小寫,而環境變量通常用大寫。因此,對於選項名 proxy,對應的環境變量可以叫
BOOST_PROXY. 在分析時,我們需要執行相反方向的名字轉換。這通過將選定的前綴作為第二個參數傳遞到 parse_environment 函數來完成。也就是說,如果你將 BOOST_ 作為前綴傳遞,且有兩個環境變量, CVSROOT 和 BOOST_PROXY, 則第一個變量將被略過,而第二個會轉換為選項 proxy.
以上邏輯對於多數情況已經足夠,但是也可以將一個接受 std::string 且返回
std::string 的函數作為第二參數傳遞給 parse_environment 函數。對於每個環境變量,傳入的函數將被調用以返回對應的選項名,或者在要忽略變量時返回空串。
下表給出了本庫所有的重要符號,便於你快速查找。
| 符號 | 描述 |
|---|---|
| 選項描述組件 | |
options_description |
描述選項的數量 |
value |
定義選項的值 |
| 分析器組件 | |
parse_command_line |
分析命令行 (簡單接口) |
basic_command_line_parser |
分析命令行 (擴展接口) |
parse_config_file |
分析配置文件 |
parse_environment |
分析環境變量 |
| 存儲器組件 | |
variables_map |
選項值的存儲 |
| Copyright c 2002-2004 Vladimir Prus |