最令人煩惱的解析
外觀
最令人煩惱的解析 (most vexing parse)是C++編程語言中的一種反直覺的二義性解析形式。 在一些場景下,編譯器無法區分某語句是初始化時某對象的參數,還是聲明一個函數時指定參數類型。在這些情況下,編譯器將該行解釋為函數聲明。
出現
[編輯]術語"most vexing parse" 最初由Scott Meyers用於2001年的書籍 Effective STL中。[1] 儘管在C語言中不常見,但這種現象在 C++ 中很常見,直到C++11推出了統一初始化才得以解決。[2]
示例
[編輯]C風格強制類型轉換
[編輯]一個例子如下:
void f(double my_dbl) {
int i(int(my_dbl));
}
上面的第 2 行是有歧義的。一種可能的解釋是聲明一個變量 i
,初始值通過轉換my_dbl
到一個int
而來。但是,C 允許在函數參數聲明周圍使用多餘的括號;因此,聲明的i
實際上等同於以下代碼:
// A function named i takes an integer and returns an integer.
int i(int my_dbl);
未命名的臨時對象
[編輯]一個更可能出現的例子是:
struct Timer {};
struct TimeKeeper {
explicit TimeKeeper(Timer t);
int get_time();
};
int main() {
TimeKeeper time_keeper(Timer());
return time_keeper.get_time();
}
其中
TimeKeeper time_keeper(Timer());
是有歧義的,它可以被解釋為:
- 一個變量:定義為類
TimeKeeper
的變量time_keeper
,用類Timer
的匿名實例初始化。 - 一個函數聲明:聲明了一個函數
time_keeper
,返回一個TimeKeeper
,有一個(未命名的)參數。參數的類型是一個(指向)不接受輸入並返回Timer
對象的函數(的指針)[Note 1] 。
C ++標準採取第二種解釋,這與上面的第9行不一致。例如,Clang++警告第9行存在最令人煩惱的解析,並報錯:[3]
$ clang++ time_keeper.cc timekeeper.cc:9:25: warning: parentheses were disambiguated as a function declaration [-Wvexing-parse] TimeKeeper time_keeper(Timer()); ^~~~~~~~~ timekeeper.cc:9:26: note: add a pair of parentheses to declare a variable TimeKeeper time_keeper(Timer()); ^ ( ) timekeeper.cc:10:21: error: member reference base type 'TimeKeeper (Timer (*)())' is not a structure or union return time_keeper.get_time(); ~~~~~~~~~~~^~~~~~~~~
解決方案
[編輯]這些有歧義的聲明往往不會被解析為程序員所期望的語句。[4][5] C++ 中的函數類型通常隱藏在typedef之後,並且通常具有顯式引用或指針限定符。要強制扭轉解析的結果,常見做法是換一種不同的對象創建或轉換語法。
在類型轉換的示例中,有兩種替代語法:「C 風格強制類型轉換」
// declares a variable of type int
int i((int)my_dbl);
或一個static_cast轉換:
int i(static_cast<int>(my_dbl));
在變量聲明的示例中,首選方法(自 C++11 起)是統一(大括號)初始化。[6] 這也允許完全省略類型名稱:
//Any of the following work:
TimeKeeper time_keeper(Timer{});
TimeKeeper time_keeper{Timer()};
TimeKeeper time_keeper{Timer{}};
TimeKeeper time_keeper( {});
TimeKeeper time_keeper{ {}};
在 C++11 之前,強制獲得預期解釋的常用手段是使用額外的括號或拷貝初始化:[5]
TimeKeeper time_keeper( /*Avoid MVP*/ (Timer()) );
TimeKeeper time_keeper = TimeKeeper(Timer());
後一種寫法中,拷貝賦值運算符 有可能被編譯器優化。[7] 自C++17開始,這種優化受到保證。[8]
註記
[編輯]- ^ 根據C++類型退化規則,作為參數聲明的函數等價於一個指向同類型函數的指針。參見C++函數對象的實例。
參考資料
[編輯]- ^ Meyers, Scott. Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library. Addison-Wesley. 2001. ISBN 0-201-74962-9.
- ^ Coffin, Jerry. c++ - What is the purpose of the Most Vexing Parse?. Stack Overflow. 29 December 2012 [2021-01-17]. (原始內容存檔於2021-11-25).
- ^ Lattner, Chris. Amazing Feats of Clang Error Recovery. LLVM Project Blog. The Most Vexing Parse. 5 April 2010 [2021-01-17]. (原始內容存檔於26 September 2020).
- ^ DrPizza; Prototyped; wb; euzeka; Simpson, Homer J. C++'s "most vexing parse". ArsTechnica OpenForum. October 2002 [2021-01-17]. (原始內容存檔於2021-11-25).
- ^ 5.0 5.1 Boccara, Jonathan. The Most Vexing Parse: How to Spot It and Fix It Quickly. Fluent C++. 2018-01-30 [2021-01-17]. (原始內容存檔於2021-11-25) (美國英語).
- ^ Stroustrup, Bjarne. C++11 FAQ. www.stroustrup.com. Uniform initialization syntax and semantics. 19 August 2016 [2021-01-17]. (原始內容存檔於2021-08-20) (英語).
- ^ Myths and urban legends about C++. C++ FAQ. What is copy elision? What is RVO?. [2021-01-17]. (原始內容存檔於2021-11-25).
- ^ Devlieghere, Jonas. Guaranteed Copy Elision. Jonas Devlieghere. 2016-11-21 [2021-01-17]. (原始內容存檔於2021-11-25) (英語). Note, however, the caveats covered in Brand, C++. Guaranteed Copy Elision Does Not Elide Copies. Microsoft C++ Team Blog. 2018-12-11 [2021-01-17]. (原始內容存檔於2021-11-25) (美國英語).
外部連結
[編輯]- Discussion in the C++03 standard final draft (see §8.2 Ambiguity resolution [dcl.ambig.res]): https://web.archive.org/web/20141113085328/https://cs.nyu.edu/courses/fall11/CSCI-GA.2110-003/documents/c++2003std.pdf
- CppReference on direct initialization (the sort vulnerable to the most vexing parse): https://en.cppreference.com/w/cpp/language/direct_initialization (頁面存檔備份,存於網際網路檔案館)