最令人烦恼的解析
外观
最令人烦恼的解析 (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 (页面存档备份,存于互联网档案馆)