介绍与引入
最近在阅读C++ Concurrency in Action 2rd edition,在里面看到一个很有意思的说法叫做most vexing parse,是有关于C++中一种违反直觉的语法歧义解析现象。在某些情况下,C++语法解析器无法区分以下二者
- The creation of an object parameter,即对象参数的创建
- The specification of a function's type,即函数类型的规约。
发生歧义时,编译器解释为第2种情况,该现象也被称为most vexing parse,这种现象直到C++11 引入uniform initialization后才得到解决。
例一:C风格类型转换
考虑我们要进行一次C风格的强制类型转换
void f(double my_dbl) {
int i(int(my_dbl));
}
第2行就有着多种解释。
- 新建一个
int
型变量,并用强制类型转换的my_db1
进行初始化 。 - 声明一个形如下的函数 ,这是由于C语言允许参数被多余的括号包裹着。
int i (int my_db1);
例二:匿名的临时变量
一个更加详细且更常见的例子为:
struct Timer {};
struct TimeKeeper {
explicit TimeKeeper(Timer t);
int get_time();
};
int main() {
TimeKeeper time_keeper(Timer());
return time_keeper.get_time();
}
对于main
函数中的
TimeKeeper time_keeper(Timer());
也有着多种解释。
- 声明一个变量
time_keeper
为类TimeKeeper
的实例,且用一个匿名类Timer()
初始化 - 声明一个函数
time_keeper
,其有一个匿名参数,参数类型为“一个无参数的,返回值类型为Timer
对象的函数指针”,返回值类型为TimeKeeper
的类对象。
C++标准采用第二种解释,则main
函数的返回
return time_keeper.get_time();
将类型不匹配。编译器选择g++,在CLion中会有如下错误提示:
可见一个好的IDE真的很重要(逃),否则编译期间也会报错。
解决方法
对于例一,有2种解决方法。
第一种,便是使用不一样的强制类型转换语法(实际上就是换个括号位置)
int i((int)my_dbl);
或者使用named cast
int i(static_cast<int>(my_dbl));
对于例二,在变量的声明中,首选方法(自C++11开始)是uniform initialization(即使用大括号)。以下任意一种方式都是正确的
TimeKeeper time_keeper(Timer{});
TimeKeeper time_keeper{Timer()};
TimeKeeper time_keeper{Timer{}};
TimeKeeper time_keeper( {});
TimeKeeper time_keeper{ {}};
在 C++11 之前,常用的方法是使用额外的括号或复制初始化
TimeKeeper time_keeper((Timer()));
TimeKeeper time_keeper = TimeKeeper(Timer());
结语
尽管现在的IDE已经很智能了,可是难免还是会面临着不能用IDE的场景,有时候了解一下这个也挺有意思的,就没听说过Java还有这种歧义(当然也是我孤陋寡闻)。