Qt 為什麼要有 QScopedPointer 呢?

不久前在 Qt Labs Blogs 上的一篇文章引起了我的注意,文章標題是:Introducing QScopedPointer ,介紹了即將在 Qt 4.6 現身的新成員 — QScopedPointer , Qt 4.6 的 preview 也已經放進了相關的文件。在 C++ 中使用 RAII 的手法是相當常見的,而且也納入標準之中的,或是 boost 也提供了 Smart Pointers library

為什麼 Qt 需要提供自己一套 Smart pointers 呢?

Qt 背後的動機令我感到好奇,一如我在 KAutoPointer: a new autoptr class for QPointer 討論串上看到鄉民 Ingo Klöcker 說的:

... Unfortunately, there appears to be quite some aversion against using boost's pointer template classes (a trivial compile time dependency) in KDE …

而的確,這也引起了一陣討論,下面收集了一些討論來瞭解一下:

  1. QT Labs Blogs:
    1. Introducing QScopedPointer
    2. Count with me: how many smart pointer classes does Qt have
    3. Some more pointing
  2. Mailing list or news
    1. KAutoPointer: a new autoptr class for QPointer
    2. OS news: Introducing QScopedPointer

題外話一下:有沒有興趣猜猜 Qt 提供了幾種 smart pointers 呢?在 Count with me: how many smart pointer classes does Qt have? 一文中列出了八種:

  1. QPointer (4.0)
  2. QSharedDataPointer (4.0)
  3. QExplicitlySharedDataPointer (4.3/4.4)
  4. QtPatternist::AutoPtr (internal class, 4.4)
  5. QSharedPointer (4.5)
  6. QWeakPointer (4.5)
  7. QGuard (internal class, 4.6)
  8. QScopedPointer (4.6)

呵呵(或,科科),是不是很有趣呢?回到正題, Count with me: how many smart pointer classes does Qt have? 一文解釋了為什麼不採用既有的 scoped pointers:

  1. C++0x 尚未通過,其實作也只有在 GCC 4.3 之後以及 MSVC 2010 beta 中才有;
  2. TR1 的實作品不夠;
  3. Boost 呢,ㄜ,Qt 認為它是“un-Qt-ish” API,或者該說是“horrible API”,這句話其實是沒有惡意的,只是想說兩者的命名哲學不同。此外,Qt 與 KDE 對於 binary compatibility 有著嚴格、複雜的要求,若是採用 boost 實作會破壞這相容性。(如果有興趣,可以參考一下 KDE TechBase 上的一篇文章:Policies/Binary Compatibility Issues With C++ ,最近的貢獻者就是 Count with me … 一文的作者: Thiago Macieira。)
  4. Qt 還是希望握有 scoped pointer 的 knowledge。

在我看來, Qt 期許自己成為一個成熟易用又跨平台的函式庫,它的支援環境可能沒有完整的標準 C++ 函式庫,又因為 C++ 缺乏統一的 ABI 支援,在某些應用上若沒有一些事先規範會讓事情發展到某種程度後窒礙難行,至於有多窒礙難行呢?我還不能說個所以然,因為自己沒有真的痛過,無法把那種罄竹難書講得感同身受 Orz …

延伸閱讀:

一些關於 ABI 、binary compatibility、modular mechanism 的討論:

  1. Qt Labs Blogs: Some thoughts on calling convention
  2. Qt Labs Blogs: Some thoughts on binary compatibility
  3. Imperfect C++: Part 2 - Surviving The Real World
  4. Essential COM: COM as a Better C++
  5. C++0x 有過相關的討論:
    1. Modules in C++
    2. Draft Proposal for Dynamic Libraries in C++
    3. 不過被打槍了!

Qt 4.5.2

自從 Qt 被 Nokia 併購後,似乎越來越活耀(?!),網站也改版了,不過以前的都會把 source code 版本直接放一個 link 出來,現在好像得去 ftp 下載,

Qt_FTP

編譯

官方的 binary 好像(不確定,我只試過 Qt SDK: Complete Development Environment 這包而已)只有給 mingw32 用的,如果想在 Windows 搭配 Visual C++ 使用,可以自己 compile ,請下載 qt-win-opensource-src-4.5.2.zip 這個檔案,不要下載 qt-all-opensource-src-4.5.2.tar.gz,編譯時可能會有下面的錯誤:

qscriptdebuggercodefinderwidgetinterface.cpp
debugging\qscriptdebuggercodefinderwidgetinterface.cpp(43) : fatal error C1083:
Cannot open include file: 'qscriptdebuggercodefinderwidgetinterface_p_p.h': No s
uch file or directory
qscriptdebuggercodefinderwidget.cpp
debugging\qscriptdebuggercodefinderwidget.cpp(43) : fatal error C1083: Cannot op
en include file: 'qscriptdebuggercodefinderwidgetinterface_p_p.h': No such file
or directory
qscriptdebugoutputwidgetinterface.cpp
qscriptdebugoutputwidget.cpp
qscriptbreakpointswidgetinterface.cpp
qscriptbreakpointswidget.cpp
qscriptbreakpointsmodel.cpp
qscripterrorlogwidgetinterface.cpp
qscripterrorlogwidget.cpp
qscriptenginedebugger.cpp
Generating Code...
NMAKE : fatal error U1077: '"C:\Program Files\Microsoft Visual Studio 8\VC\BIN\c
l.EXE"' : return code '0x2'
Stop.
NMAKE : fatal error U1077: '"C:\Program Files\Microsoft Visual Studio 8\VC\BIN\n
make.exe"' : return code '0x2'
Stop.
NMAKE : fatal error U1077: 'cd' : return code '0x2'
Stop.

而且編一次都要很久,禁不起這種錯誤打擊的!編譯可以使用這個指令,是我試過 ok 的:

configure -debug-and-release -no-sql-sqlite -no-qt3support -no-opengl -platform win32-msvc2005 -no-libtiff -no-dbus -no-phonon -no-phonon-backend -no-webkit 

其中最重要的是要指定 -debug-and-release ,因為預設是不會產生 debug 版本,有時會有 debug 或是 trace 上的困擾。如果電腦上安裝多個開發環境,請透過 -platform win32-msvc2005 指定 toolchain

nmake 後就是看 CPU 運動了!不過 vc 似乎無法吃光四核心 + 4G Ram 的效能 :(

好多 nmake

Qt_nmake

一個 cl

Qt_cl

吃不了什麼 CPU 和 RAM

Qt_CPU_Usage

The C++0x "Remove Concepts" Decision

正如 COdEfr3@K 所說:大件事!花了點時間 survey 一些網站~

  1. 大師說話了
    1. Bjarne Stroustrup: The C++0x “Remove Concepts” Decision
    2. Doug Gregor: What Happened in Frankfurt? (由 COdEfr3@K 補充)
    3. Herb Sutter: Trip Report: Exit Concepts, Final ISO C++ Draft C++ Draft in ~18 Months
    4. Bjarne Stroustrup Expounds on Concepts and the Future of C++ 
    5. Danny Kalev:
      1. The Removal of Concepts From C++0x
      2. The Rise and Fall of C++0x Concepts
      3. The Future of C++ 0x, Part 1
    6. The View (or trip report) from the July 2009 C++ Standard meeting
    7. The View (or trip report) from the July 2009 C++ Standard meeting (Part 2)
    8. Concepts Removed from C++0x
  2. 鄉民的討論
    1. Concepts Get Voted Off The C++ 0x Island
    2. The Removal of Concepts From C++0x
    3. TopLanguage: {C++} concept给毙了:(
  3. 翻譯~ 
    1. Concept 的道路
    2. Concept 失敗的原因

下面是從 DDJ 翻譯的,讓 Bjarne 說說看整個事件吧 : )

The C++0x "Remove Concepts" Decision

在 2009 七月的德國法蘭克福會議上, C++ Standards Committee 決定將 concepts 移出 C++0x。雖然這對那些致力於 concepts 多年以及熟知 Concepts 潛力的人們來說是個沈重的打擊,但幸運地,移除 concepts 對大多數的程式設計師並不太有直接的影響。對於現實世界的軟體開發, C++0x 和 C++98 相比,仍舊是個更極具表達力(expressive)和更有效用的(effective)語言。 Committee 對於 remove concepts 的用意:在於希望能限制它帶能的風險與確保整個時程(The committee acted with the intent to limit risk and preserve schedule),或許更好的 concepts 會在未來五年後出現。這篇文章將簡述移除 concepts 的理由以及 committee 做出此決定背後的的考量,此外也讓大家知道 C++ 的天還沒有塌下來啊~(儘管大把大把的謠言剛好說得相反!)

No "Concepts" in C++0x

2009 七月 ISO C++ Standards Committee (WG21) 的法蘭克福會議上,在討論 concepts 應用在 specifying requirements for template arguments 時被"脫鉤(decoupled)"了,對於我來說,一個比較不那麼外交的用語是被"猛烈扯開(yanked out)"了,這代表 concepts 不會出現 C++0x 或是它的 library 裡頭,在我看來,這是 C++ 的一大挫敗,但不是災難一場(disaster);一些替代方法都只會更糟。(That -- in my opinion -- is a major setback for C++, but not a disaster; and some alternatives were even worse)

我在 concepts 已經努力超過七年了,而花了更甚於此的時間在解決這類問題。許多對於 concepts 的工作已經非常久了,舉例來說(按年代排序):

唉,這不用說,我跟其他人對於這件事感到相當失望,而事實上,一些替代方案都只是更糟、於事無補的,而我也無法提供快速、便利的解法。

請注意到 C++0x 對於 C++ features 的改善與進步對大多數程式設計師看到和使用到的是沒有影響的。C++0x 與 C++98 相比,將仍舊是更具表達力的(expressive),它支援了:

  • Concurrent programming
  • A better standard library
  • 許多的改善使得寫出好程式碼更加容易、簡單(像是更有效率、更好維護)

尤其,對於那些我給的 C++0x 範例,要是沒用到 concepts 或是 requires 的更是不受影響(像是在 Evolving a language in and for the real world: C++ 1991-2006 at ACM HOPL-III )。請參考我寫的 C++0x FAQ,一些人們甚至對於 C++0x 比他們想像中的還簡單而感到歡欣。

Concepts 曾經是 C++0x 對於 template 有更好的理論基礎的重要核心 feature,像是說:

  • 提供更加穩固的 standard library specification
  • 扮演讓泛型程式設計(generic programming)成為主流使用的核心角色

現在,人們將在沒有語言的直接支援下使用 concepts。我對於未來最好的想像(scenario)是:我們在五年後的 C++ 裡頭得到了比現有 concepts 更好的 concepts。要能得到這樣的成果將會需要許多人非常專注的研究(但可不是"design by committee"喔!)。

What Happened?

Concepts 在過去幾年被發展出來,並在 2008 年,經過某些技術上的妥協後,進入到了 C++0x working paper 裡頭(妥協是自然而且必須的)。在實驗性的實作中已經足以應付"conceptualized" standard library,但還不到產品品質(but was not production quality),但我個人認為已經足夠稱為 proof of concept (POC)了!

(此段怕誤導、失真,段落後附有原文節錄)

我跟幾位 members 對於 concepts 有著同樣的考量:

Concepts 的設計,尤其是可用性(usability)這點,是否可為一般普通的程式設計師所掌握

會這麼說,是因為這決定了 concepts 設計的目標:

使 generic progarmming 可被大多數的程式設計師接受[BS&GDR2003]

但這目標在我現在看來已經做了極大的妥協:

Concepts 已經變成了(只存在)專家手上的工具,而不是一個讓 generic programming 更加可被接受的工具了。

過去一年半裡,我試著從使用者的角度去檢驗 C++0x,我擔心即用了 concepts 加持的 libraries 仍然對於程式設計師設下了一道新門檻。我覺得 concepts 的設計和它在 standard library 的使用並沒有充分反應我們過去幾年對於 concepts 的經驗。

「原文節錄」:My concern was with the design of "concepts" and in particular with the usability of "concepts" in the hands of "average programmers." That concern was shared by several members. The stated aim of "concepts" was to make generic programming more accessible to most programmers [BS&GDR2003], but that aim seemed to me to have been seriously compromised: Rather than making generic programming more accessible, "concepts" were becoming yet another tool in the hands of experts (only). Over the last half year or so, I had been examining C++0x from a user's point of view, and I worried that even use of libraries implemented using "concepts" would put new burdens on programmers. I felt that the design of "concepts" and its use in the standard library did not adequately reflect our experience with "concepts" over the last few years.

接著,幾個月以前,Alisdair Meredith (一位極具洞察力的 UK committee)和 Howard Hinnant (the head of the standard library working group)問了幾個很好的問題,問題是關於:

  • 誰應該直接使用到 concepts、
  • 使用 concepts 哪個部份、
  • 如何使用

「原文節錄」:who should directly use which parts of the "concepts" facilities and how

這問題導引出各方考量以及不同角度出發對於 usability 的討論。最後,在經過多次令人混淆的討論後,我把我的結論放在 [BS2009]。

幾個可能有點過度簡化的結論:

  • 現行定義的 concepts 難以使用,並將導致 concepts 不被使用(disuse),這可能是 template 不被使用或是 C++0x 不被公開採納。
  • 一些簡化方法[BS2009]仍舊可以描述 concepts ,使得 C++0x 可以出貨或是僅須稍稍的修改。

請注意到:這只是個非常簡略、囫圇吞棗的結論,standards committee 在討論時是非常有 polite 的,我們有著同樣的目標,總是避免直接的針鋒相對、對質詢問。不幸的是,當時的(內部)討論實在過於龐雜(許許多多牽涉到細節或沒有牽涉到細節的訊息)、令人困惑,在對於問題的定位及如何處理沒有浮現共識下,我在法蘭克福會議裡頭報告了幾項替代方案:

  • "fix and ship"
    • Remaining work: remove explicit "concepts," add explicit refinement, add "concept"/type matching, handle "concept" map scope problems
    • Risks: no implementation, complexity of description
    • Schedule: no change or one meeting
  • "Yank and ship"
    • Remaining work: yank (core and standard library)
    • Risks: old template problems remain, disappointment in "progressive" community ("seven year's work down the drain")
    • Schedule: five years to "concepts" (complete redesign needed) or never
  • "Status quo"
    • Remaining work: details
    • Risks: unacceptable programming model, complexity of description (alternative view: none)
    • Schedule: no change

我和一些其他的人贊同第一個方案("fix and ship")並認為這是可行的。然而,大多數的 committee 不是這麼認為,他們選擇了第二個方案("yank and ship," renaming it "decoupling")。在我看來兩者都比第三個方案("status quo")好,我對於投票結果的解讀是這樣的:(這段怕有情緒性字眼,也保留原文)

  • 對於 concepts 的支持者而言,他們沒有一個共識,整個想法看起來太過爭議
  • 一些人則是擔心 C++0x 雄心勃勃的時程表開始(ㄜ,在我不公正地看來,開始指責 concepts)
  • 一些人則是從頭到尾對於 concepts 就是興致缺缺

「原文節錄」:My interpretation of that vote is that given the disagreement among proponents of "concepts," the whole idea seemed controversial to some, some were already worried about the ambitious schedule for C++0x (and, unfairly IMO, blamed "concepts"), and some were never enthusiastic about "concepts."

有鑑於此,"fixing concepts"不再是一個迫於現實的選則了,本質來說,所有對於 concepts 的支持都將是更久之後的事("later" and "eventually")。我對於現在把 concepts 移除這件事也提出了會有無可避免的延遲的警告, 因為在沒有時程的壓力下,所有的設計抉擇都將被重新評估(譯注:我想是對於 concepts 的設計抉擇)。

「原文節錄」:I warned that a long delay was inevitable if we removed "concepts" now because in the absence of schedule pressures, essentially all design decisions will be re-evaluated.

令人驚訝的(或許吧),在法蘭克福的會議上沒有關於 concepts 的技術報告和討論。整個討論都只著重在時間問題上,我想這投票結果主要反應時間考量吧~

請不要譴責 committee 的謹慎,這不是 "Bjarne vs. the committee fight", 而是試著在大多數人的考量中取得平衡點。我跟其他人會失望,是因為我們沒有抓住"fix and ship"這個機會,而且 C++ 不是一個學院派、實驗性質的語言,除非成員們相信這麼做對於程式碼撰寫上帶來的風險非常低,否則他們就得、也必須拒絕接受它。整體來說,committee 代表的是數十億行程式碼,舉例來說,如果 C++0x 無法被公開採納或是無法在有 concepts 的情況下讓 unconstrained templates 得到長期的使用,C++ 社群將會分崩離析。因此,一個很虛(poor)的 concepts 設計可能會比沒有 concepts 帶來更糟的後果。兩難之間,我也選擇了移除 concepts ,我寧願選擇挫敗也不要一個災難來臨。

Technical Issues

懸而未解的 concepts,其問題在於,當把焦點放到 distinction between explicit and implicit "concept" maps 時(參考 [BS2009]):

  1. Should a type that meets the requirements of a "concept" automatically be accepted where the "concept" is required (e.g. should a type X that provides +, -, *, and / with suitable parameters automatically match a "concept" C that requires the usual arithmetic operations with suitable parameters) or should an additional explicit statement (a "concept" map from X to C) that a match is intentional be required? (My answer: Use automatic match in almost all cases).
  2. Should there be a choice between automatic and explicit "concepts" and should a designer of a "concept" be able to force every user to follow his choice? (My answer: All "concepts" should be automatic).
  3. Should a type X that provides a member operation X::begin() be considered a match for a "concept" C<T> that requires a function begin(T) or should a user supply a "concept" map from T to C? An example is std::vector and std::Range. (My answer: It should match).

我的建議和在"status quo before Frankfurt" 提到的解法(answer)是完全不同的。很明顯地,在這裡我必須忽略大部份的細節和設計理念、簡化一下我的解釋。

我無法在這裡重現整個技術討論,但是下面是我的結論:在 "status quo" 的設計裡頭, "concept" maps 被用來做到兩件事:

  • To map types to "concepts" by adding/mapping attributes and
  • To assert that a type matches a "concept."

不知何故(Somehow),後者未被視作不幸的、少見的必要性,反而被視為基本的功能。當兩個 concepts 有著語意上的差別時,我們需要的不是一個用來說明這個 type 符合這個 concept 但不符合那個 concept 的 assertion (這充其量,只是種 workaround,一種不直接接、用說明回擊問題本身的手法,原文:this is, at best, a workaround -- an indirect and elaborate attack on the fundamental problem),而是一個 assertion 可以說明這 type 帶有與這 concept 相同的語意,但沒有另外一個  concept 的語意。(fulfills the axiom(s) of the one and not the other "concept")

「原文節錄」:Somehow, the latter came to be seen an essential function by some people, rather than an unfortunate rare necessity. When two "concepts" differ semantically, what is needed is not an assertion that a type meets one and not the other "concept" (this is, at best, a workaround -- an indirect and elaborate attack on the fundamental problem), but an assertion that a type has the semantics of the one and not the other "concepts" (fulfills the axiom(s) of the one and not the other "concept").

舉例來說, STL 的 input iterator 和 forward iterator 有著關鍵性的語意差別:

You can traverse a sequence defined by forward iterators twice, but not a sequence defined by input iterators;

像是,在 input stream 上施行 multi-pass algorithm 不是個好主意!在 "status quo" 解法中,會強迫每個使用者說明:

What types match a forward iterator and what types match an input iterator

而我的建議的解決方式是:

若且唯若(If (and only if))

  1. 你想使用的語意並不是兩個 concepts 的共同部份,
  2. 而且 compiler 無法幫你推導出哪個 concept 才是較好的 metch 時,

你必須說明 type 的語意,像是說:

"my type supports multi-pass semantics."

其他人則可能是這麼說:

"When all you have is a 'concept' map, everything looks like needing a type/'concept' assertion."

在法蘭克福的會議上,我是這樣做結論的:

  • Why do we want "concepts"?
    • To make requirement on types used as template arguments explicit
      • Precise documentation
      • Better error messages
      • Overloading

不同的人們有不同的出發點以及排出不同的優先順序,然而,站在這高階的角度來看,可能會有些混淆,但應該是極少或不相衝突的。每個合理的、才走一半的 concept design 都提供了這些特性。

  • What concerns do people have?
    • Programmability
    • Complexity of formal specification
    • Compile time
    • Run time

我個人關注的是 programmability (ease of use, generality, teachability, scalability) ,其次是正式規格書的 complexity (40 頁的標準文件),再其他則關於 compiler time 和 run time 的考量。然而,我覺得實驗性質的實作(ConceptGCC [Gregor2006])中,constrained templates (使用 concepts)的 run time 可以做的跟 nonconstrained templates 一樣好、甚至更好。ConceptGCC 實際上非常慢,但我並未將它視為根本考量,當它用來驗證一個想法時,我們陷入了傳統上的兩難。這裡描述一下有點過度簡化的兩難問題:

  • "Don't standardize without commercial implementation"
  • "Major implementers do not implement without a standard"

各方面來說,細節設計和實驗實作構成了基本的妥協。

我對 concepts 的原則是:

  • Duck typing
    • The key to the success of templates for GP (compared to OO with interfaces and more)
  • Substitutability
    • Never call a function with a stronger precondition than is "guaranteed"
  • "Accidental match" is a minor problem
    • Not in the top 100 problems

我對於 concepts 的"minimal fixes"寫在 pre-Frankfurt working paper:

  • "Concepts" are implicit/auto
    • To make duck typing the rule
  • Explicit refinement
    • To handle substitutability problems
  • General scoping of "concept" maps
    • To minimize "implementation leakage"
  • Simple type/"concept" matching
    • To make vector a range without redundant "concept" map

細節請參考 [BS2009]。

No C++0x, Long Live C++1x

即使在 concepts 已經被移除後,下一版的 C++ standard 仍可能延遲推出。悲慘地說,將不會有 C++0x 了(除非你把 C++0x 這個小改版算進去)。我們一定得等到 C++1x,希望這個 x 是個很小的數字。希望還是在的,因為 C++1x 已經 feature complete 了(除非標準對於一些在 formal proposal for the standard 還有所堅持)。 "所有" 剩下、懸而未決的都只是龐雜的技術問題和意見了。

在我的 C++0x FAQ 中你可以看到一些 features 和討論,這裡僅是列出部份:

和 libraries:

  • Improvements to algorithms
  • containers
  • duration and time_point
  • function and bind
  • forward_list a singly-liked list
  • future and promise
  • garbage collection ABI
  • hash_tables; see unordered_map
  • metaprogramming and type traits
  • random number generators
  • regex a regular expression library
  • scoped allocators
  • smart pointers; see shared_ptr, weak_ptr, and unique_ptr
  • threads
  • atomic operations
  • tuple

    即使沒有了 concepts,C++1x 仍將會是個基於 C++98 大量改善的版本,尤其當你注意到這些 features 都讓你可以在最大的表達力(expressiveness)和彈性(flexibility)間的互動的。我希望我們會在新版的 C++ (或許五年後)中看到 concepts ,也或許我們應該稱呼 C++1y 或是 C++y!

    心得

    該看書了 >__<

  • How Lord of The Rings Should Have Ended

    讓我想到小時候寫作文應該要:

    1. 小題大做
    2. 大題小做
    3. 不要做作

    Boost.Program_options – Tutorial – Option Details and Multiples Sources

    Vocabulary

    1. Program options, options
    2. pair. (name, value)
    3. Token
    4. Position options
    5. Composing options

    Option Details

    沒錯,你得到它了!option value 不僅只侷限於 int 型別,此外還可以有其它下面的屬性(proerties)。完整程式可以在

    example/options_description.cpp

    找到。

    想像你在寫一個 compiler ,你可以設定它的 options:

    1. Otimization level
    2. Include paths
    3. Input files
    4. 其它做些有趣的事。

    下面的程式碼試著描述這些 options :

    int opt;
    po::options_description desc("Allowed options");
    desc.add_options()
        ("help", "produce help message")
        ("optimization", po::value<int>(&opt)->default_value(10), 
      "optimization level")
        ("include-path,I", po::value< vector<string> >(), 
      "include path")
        ("input-file", po::value< vector<string> >(), "input file")
    ;
    1. help:就像上個範例一樣。最好讓每個 case 都有自己的 help option。
    2. optimization:這裡的 optimization 有兩個特點:
      1. 我們在程式裡頭指定了變數的位址(&opt)。如此一來,當 values 被分析儲存後,我們就可以透過該變數取得 optimization 的 value。
      2. 我們指定了變數的 default value 為 10 。因此,當使用者沒有給 optimization 指定一個 value 時, value 會是 10。
    3. include-path:include-path 在這裡是一個特殊的例子,它只從 command line 讀入資料來使用 options_description。(原文是:The "include-path" option is an example of the only case where the interface of the options_description class serves only one source -- the command line.  看了上下文,總覺得這句有點詭異、跳 tone)。對於常用到的 options,使用者往往喜歡使用它們的 short option name 形式,而這也就是程式碼裡頭 "include-path,I" option name 想表達的意思:"I" 是 include-path 的 short name。
      另外,值得注意的是 "include-path" 的型別是 std::vector。Boost.Program_options 對於 vectors 有特別的支援,它可以多次出現在 option 輸入中,而所有的 values 都會被存放在一個 vector 裡頭。
    4. input-file:描述了一連串、將被處理的檔案。當然,一開始的時候像下面的方式指定 input file 是可以的:
      compiler --input-file=a.cpp
      但,如果和下面的方法相比,上面的方式似乎就有點不標準囉!
      compiler a.cpp

      我們將針對這問題來探討一下:

    像 a.cpp 這種在 parsing 的時候找不到 option name 的 token 的,在 Boost.Program_options 裡頭稱為 positional options。透過一些使用者的幫助,Boost.Program_options 有辦法可以知道 a.cpp 實際上就是 "--input-file=a.cpp" ,下面是妳/你需要的額外程式碼:

    po::positional_options_description p;
    p.add("input-file", -1);
    
    po::variables_map vm;
    po::store(po::command_line_parser(ac, av).
              options(desc).positional(p).run(), vm);
    po::notify(vm);

    開始兩行程式碼的意思是:所有的 positional options 需要被轉會成 "input-file" options。此外,這邊我們使用 command_line_parser 類別,而不是 parse_command_line function。parse_command_line 是一個只在簡單情況下會用到的 wrapper function 而已,但我們現在需要更多的資訊來做正確分析。

    現在,所有的 options 都已經被描述和分析了,我們就饒了自己吧,不要去管怎麼實作 compiler 的邏輯、細節,先用 print 的方式來表示各個 options。

    if (vm.count("include-path"))
    {
        cout << "Include paths are: " 
             << vm["include-path"].as< vector<string> >() << "\n";
    }
    
    if (vm.count("input-file"))
    {
        cout << "Input files are: " 
             << vm["input-file"].as< vector<string> >() << "\n";
    }
    
    cout << "Optimization level is " << opt << "\n";     

    下面是個編譯、執行這個範例的方法:

    $bin/gcc/debug/options_description --help
    Usage: options_description [options]
    Allowed options:
      --help                 : produce help message
      --optimization arg     : optimization level
      -I [ --include-path ] arg : include path
      --input-file arg       : input file
    $bin/gcc/debug/options_description
    Optimization level is 10
    $bin/gcc/debug/options_description --optimization 4 -I foo a.cpp
    Include paths are: foo
    Input files are: a.cpp
    Optimization level is 4

    歐喔,還有一個小問題!我們還是可以使用"input-file"來指定,而 Usage 也是這樣寫的,這可能會混淆了我們的使用者,如果能把這資訊隱藏應該會不錯,不過就留到下一個例子吧。

    Multiptles Sources

    很快地,妳/你就會發現都得把所有的 compiler options 填好是一件令人抓狂的事。想想看,難道妳/你希望使用者裝了一個新的 library 後每次使用妳/你的 compiler 都得一個一個把 options 填好嗎?如果能她/他決定了一些選項是每次都要使用到的呢?最好的情況是產生一個 common settings 然後搭配 command line 來使用。

    當然,把 config file 跟 command line 搭配使用是有必要的。舉例來說:config file 的 optimization level 會被 command line 的輸入蓋過;然而 include path 卻是需要有累加效果的。

    來看個 code 先,完整的程式可以在

    example/multiple_sources.cpp

    找到。option 定義有兩個有趣的點:

    1. 我們宣告了多個 options_descriptions 實體(instances)。為什麼呢?因為不是所有的 options 都是相近的,有些 options,像是 input-file 不應該出現在自動化 help message 裡頭;有些 options 只在 config files 裡頭才有意義。
    2. 最後,讓 help message 有點結構化、而不只是串列表是不錯的主意。

    下面讓我們宣告幾個 option groups 吧:

    // Declare a group of options that will be 
    // allowed only on command line
    po::options_description generic("Generic options");
    generic.add_options()
        ("version,v", "print version string")
        ("help", "produce help message")    
        ;
        
    // Declare a group of options that will be 
    // allowed both on command line and in
    // config file
    po::options_description config("Configuration");
    config.add_options()
        ("optimization", po::value<int>(&opt)->default_value(10), 
              "optimization level")
        ("include-path,I", 
             po::value< vector<string> >()->composing(), 
             "include path")
        ;
    
    // Hidden options, will be allowed both on command line and
    // in config file, but will not be shown to the user.
    po::options_description hidden("Hidden options");
    hidden.add_options()
        ("input-file", po::value< vector<string> >(), "input file")
        ;        

    請注意到宣告 include-path option 時呼叫的 composing method,這告訴了 library 不同來源的 values 應該被組合在一起(composed together)。

    我們可以用 options_description 的 add method 來進一步的組織 option groups 中:

    po::options_description cmdline_options;
    cmdline_options.add(generic).add(config).add(hidden);
    
    po::options_description config_file_options;
    config_file_options.add(config).add(hidden);
    
    po::options_description visible("Allowed options");
    visible.add(generic).add(config);

    分析和儲存 values 跟以往的作法相同,除了我們得額外呼叫了 parse_config_file 和呼叫 store 兩次之外。如果有些 value 在 command line 和 config file 中都出現了,那會發生什麼事?通常來說,先儲存下來的 value 會被視為優先,這也是 -optimization option 的設計,而對於 composing options 來說(像是 include-file ),這些 values 則是會被合併。

    下面是個編譯、執行這個範例的方法:

    $bin/gcc/debug/multiple_sources
    Include paths are: /opt
    Optimization level is 1
    $bin/gcc/debug/multiple_sources --help
    Allows options:
    
    Generic options:
      -v [ --version ]       : print version string
      --help                 : produce help message
    
    Configuration:
      --optimization n       : optimization level
      -I [ --include-path ] path : include path
    
    $bin/gcc/debug/multiple_sources --optimization=4 -I foo a.cpp b.cpp
    Include paths are: foo /opt
    Input files are: a.cpp b.cpp
    Optimization level is 4

    第一個呼叫是使用 config file。第二個則多使用了 command line 的輸入。正如我們所看到的,include path 的 value 是從 command line 和 config file 合併來的,而 optimization 則是從 command line 取得。

    Reference

    1. Boost 1.39.0 Doc: Chapter 13. Boost.Program_options

    See Also

    1. Boost.Program_options – Introduction and Tutorial 

    Boost.Program_options – Introduction and Tutorial

    最近因為需要一些自動化的程式看了 Boost.Program_options 這個 library ,這系列文章主要內容都是翻譯Boost 1.39.0 Doc 裡頭,希望還有點時間可以加上一些 trace 的心得和紀錄,希望沒有誤人子弟、教壞仔大小、對大家有幫助~

    Vocabulary

    1. Program options, options
    2. Config file
    3. Command line
    4. pair. (name, value)

    Introduction

    Boost.Program_options 是個可以讓程式設計師獲得 program options 的函式庫,什麼是 program options 呢?就是那種由使用者提供,可能是 command line 或是 config file 的 (name, value) 組合(pairs)

    那為什麼需要使用 Boost.Program_options 呢?為什麼說它比我們依靠直覺寫出來的程式碼還好呢?

    • 簡單。Boost.Program_options 很小、提供的新增 options 的語法也簡單。像是自動化的 option value 的型別轉換(type conversion)、變數存放。
    • 比較好的錯誤回報。 當分析(parsing)command line input 出錯時,Boost.Program_options 會回報所有的錯誤,但 hand-written code 可能會誤判。
    • Options 可以來自任何地方。當使用者可能對你的程式只能藉由 command line 輸入感到不滿,妳/你很快就會用到 config file 或是 environment variables 了。這些都可以幾乎無痛得透過 Boost.Program_options 來擴充。

    Tutorial

    在這個 section ,我們將從最簡單的地方開始,之後逐漸知道大部分 Boost.Program_options 的 usage scenarios 。這部份的文件都只有部份的、重要的程式碼,完整的範例可以在:

    BOOST_ROOT/libs/program_options/example

    資料夾找到。此外整個範例,都假設下面的 namesapce alias 是有效的!

    namespace po = boost::program_options

    Getting Started

    第一個也可能是最簡單的一個範例是:

    處理兩個 options

    下面是程式碼部份(完整程式可以在 example/first.cpp 找到)

    // Declare the supported options.
    po::options_description desc("Allowed options");
    desc.add_options()
        ("help", "produce help message")
        ("compression", po::value<int>(), "set compression level")
    ;

    一開始的地方宣告了一個 options_descriptions 來存放允許的 options。呼叫 options_description class 的 add_options method 會回傳一個提供了 operator() 操作的 proxy object 。呼叫 operator() 就是宣告了 options 。參數分別是:

    (option name, information about value, option description)

    這個範例裡頭,第一個 option 沒有 value,第二個 options 有一個型別是 int 的 value 。

    po::variables_map vm;
    po::store(po::parse_command_line(ac, av, desc), vm);
    po::notify(vm);    

    之後,宣告一個型別為 variables_map 的 object -- vm。variables_map 是用來存放任意型別的 value 。接著呼叫:

    1. store
    2. parse_command_line
    3. notify

    去讓 vm 獲得所有 command line 來的 value。

    if (vm.count("help")) {
        cout << desc << "\n";
        return 1;
    }
    
    if (vm.count("compression")) {
        cout << "Compression level was set to " 
     << vm["compression"].as<int>() << ".\n";
    } else {
        cout << "Compression level was not set.\n";
    }

    最後我們可以任意地使用 options 了,variable_map 提供了像是 std::map 的操作,除了取得 value 時,必須使用 as method外。(要是你不小心誤用了 as 中的型別參數,使得 as 參數想取出的型別和實際不合時,會出一個 exception)

    試著 compile 你的程式吧,下面是個範例:

    $bin/gcc/debug/first
    Compression level was not set.
    $bin/gcc/debug/first --help
    Allowed options:
      --help                 : produce help message
      --compression arg      : set compression level
    $bin/gcc/debug/first --compression 10
    Compression level was set to 10.

    Reference

    1. Boost 1.39.0 Doc: Chapter 13. Boost.Program_options

    See Also

    1. Boost.Program_options – Option Details and Multiple Sources

    Windows + Visual Studio + VSCode + CMake 的疑難雜症

    Environment Windows 10 Visual Studio 2019 CMake 3.27.7 VSCode VSCode CMake Tools 1. CMAKE_BUILD_TYPE 是空的 參考一下 這篇 的處理。 大致上因為 Visual...