跳至內容

最令人煩惱的解析

維基百科,自由的百科全書

最令人煩惱的解析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());

是有歧義的,它可以被解釋為:

  1. 一個變量:定義為類TimeKeeper的變量time_keeper,用類Timer的匿名實例初始化。
  2. 一個函數聲明:聲明了一個函數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]

註記[編輯]

  1. ^ 根據C++類型退化規則,作為參數聲明的函數等價於一個指向同類型函數的指針。參見C++函數對象的實例

參考資料[編輯]

  1. ^ Meyers, Scott. Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library. Addison-Wesley. 2001. ISBN 0-201-74962-9. 
  2. ^ Coffin, Jerry. c++ - What is the purpose of the Most Vexing Parse?. Stack Overflow. 29 December 2012 [2021-01-17]. (原始內容存檔於2021-11-25). 
  3. ^ Lattner, Chris. Amazing Feats of Clang Error Recovery. LLVM Project Blog. The Most Vexing Parse. 5 April 2010 [2021-01-17]. (原始內容存檔於26 September 2020). 
  4. ^ DrPizza; Prototyped; wb; euzeka; Simpson, Homer J. C++'s "most vexing parse". ArsTechnica OpenForum. October 2002 [2021-01-17]. (原始內容存檔於2021-11-25). 
  5. ^ 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) (美國英語). 
  6. ^ Stroustrup, Bjarne. C++11 FAQ. www.stroustrup.com. Uniform initialization syntax and semantics. 19 August 2016 [2021-01-17]. (原始內容存檔於2021-08-20) (英語). 
  7. ^ Myths and urban legends about C++. C++ FAQ. What is copy elision? What is RVO?. [2021-01-17]. (原始內容存檔於2021-11-25). 
  8. ^ 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) (美國英語). 

外部連結[編輯]