Scott Meyers 在《Effective STL》一书的第六条提出了一个“当心C++最令人烦恼的解析"(the most vexing parse)的问题。“the most vexing parse” 这个术语来形容 C++ 标准对于 declaration 语句的消歧义(ambiguity resolution)约定与常人的认知相悖。
最令人烦恼的解析 (most vexing parse)是C++中的一种反直觉的二义性解析形式。 在一些场景下,编译器无法区分某语句是初始化时某对象的参数,还是声明一个函数时指定参数类型。在这些情况下,编译器将该行解释为函数声明。简言之就是“调用构造函数被误认为是函数声明的问题”,即 形如 Type() 或 Type(name) 的表达在某些情况下具有歧义(syntax ambiguity)。
下面来看那本书中的一个例子:假设有一个存有整数int的文件,需要把这些整数拷贝到一个list中,下面是合理的做法:
std::ifstream file("test.txt");
std::list<int> data(std::istream_iterator<int>(file), std::istream_iterator<int>());
上面这段代码的思路是,把一对istream_iterator对象传入到list的区间构造函数中 (Effective STL的第五条建议:区间成员函数优先于与之对应的单元素成员函数)。
但实际上,使用g++编译上述代码,可以编译成功,但有如下的警告:
D:\Code\Git\cppconcurrent\chapter2\main.cpp: In function 'int main()':
D:\Code\Git\cppconcurrent\chapter2\main.cpp:59:24: warning: parentheses were disambiguated as a function declaration [-Wvexing-parse]
59 | std::list<int> data(std::istream_iterator<int>(file), std::istream_iterator<int>());
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
D:\Code\Git\cppconcurrent\chapter2\main.cpp:59:24: note: replace parentheses with braces to declare a variable
59 | std::list<int> data(std::istream_iterator<int>(file), std::istream_iterator<int>());
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| -
| { -
| }
如果你忽略上述警告,直接运行,会发现它什么都不会做。不会从文件中读取任何数据,不会创建list,这是因为第二条语句并没有声明一个list,也没有调用构造函数。实际上上述编译器的警告信息已经说的很清楚了。但为了详细解释,我们还是从最基本的说起。
在C++中,如果要声明一个函数 f
,参数为 double
,返回值为 int
,以下三种方法都是合法的:
int f(double d);
int f(double(d)); // 同上,d两边的括号被忽略
int f(double); // 同上,参数名被忽略
第2个声明,对行参加上圆括号,编译器会认为是冗余的并忽略。第3个则省略了行参名。
另外再看一个例子,假如我们想申明一个函数 g
,其参数是一个函数指针,下面 3 种写法都是合法的:
int g(double (*pf)()); // g以指向函数的指针为参数
int g(double pf()); // 同上,pf为隐士指针
int g(double()); // 同上,省去参数名pf
上面两个例子可以看出,围绕参数名的括号(比如f的第二个声明中的d)与独立的括号的区别:
- 围绕参数名的括号会被忽略
- 独立的括号则表明参数列表的存在,它们说明存在一个函数指针参数。
比如下面这两个语句:
T1 name(T2());
T1 name1(T2(name2));
C++都会将这两条语句视为声明:
- 对于第一个语句,C++将其视为:返回值类型为
T1
名为name
的函数(参数类型为指向返回值类型为T2
,参数为空的函数的指针)。 - 对于第二个语句,C++将其视为:
T1 name1(T2 name2);
显然也是一个函数申明。
现在再回到开头:
std::list<int> data(std::istream_iterator<int>(file), std::istream_iterator<int>());
这声明了一个函数 data
,返回值是 std::list<int>
,这个函数有两个参数:
- 第一个参数名为
file
, 它的类型是std::istream_iterator<int>
。file
两边的括号是多余的,可以忽略(见f的第2个声明)。 - 第二个参数没有名称,它的类型时一个指向不带参数的函数指针,该函数的返回类型是
std::istream_iterator<int>
,函数指针的参数名被省略了(见g的第3个声明)。
g++编译器的错误提醒,也说的很清楚,这是一个“vexing-parse”警告,它会被解析为一个函数声明:
D:\Code\Git\cppconcurrent\chapter2\main.cpp: In function 'int main()':
D:\Code\Git\cppconcurrent\chapter2\main.cpp:59:24: warning: parentheses were disambiguated as a function declaration [-Wvexing-parse]
59 | std::list<int> data(std::istream_iterator<int>(file), std::istream_iterator<int>());
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
D:\Code\Git\cppconcurrent\chapter2\main.cpp:59:24: note: replace parentheses with braces to declare a variable
59 | std::list<int> data(std::istream_iterator<int>(file), std::istream_iterator<int>());
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| -
| { -
| }
并且很贴心的给出了解决方法,那就是当初始化时,将小括号改为大括号。
std::ifstream file("test.txt");
std::list<int> data{std::istream_iterator<int>(file), std::istream_iterator<int>()};
针对这一问题的解决方法有多种:
- 多增加一对括号来跳过编译器的分析机制!(将形参的声明用括号括起来是非法的,但给函数参数加上括号却是合法的),将第一个参数多增加一个括号。
std::list<int> data((std::istream_iterator<int>(file)), std::istream_iterator<int>());
- 为临时对象取名:
std::ifstream file("test.txt"); std::istream_iterator<int> begin=std::istream_iterator<int>(file); std::istream_iterator<int> end; std::list<int> data(begin, end);
- 使用大括号“{}”初始化。如上,可以看到g++编译器在发现"-Wvexing-parse" 警告时,建议的方法就是使用大括号来进行初始化。
在C++ Concurrency in Action中也有一个例子:
#include <iostream>
#include <thread>
class background_task
{
public:
void operator()() const
{
std::cout << "do something!" << std::endl;
std::cout << "do something else!" << std::endl;
}
};
int main()
{
std::thread t(background_task());
t.join();
return 1;
}
比如上述代码,在本意为初始化一个thread对象时,却意外定义了一个名为t的函数,返回值为thread,函数参数为一个函数指针,该函数指针参数为空,返回类型为back_ground_task,编译器的报错也很明线,t是一个函数声明,根本没有join方法。
expression must have class type but it has type "std::thread (*)(background_task (*)())"
request for member 'join' in 't', which is of non-class type 'std::thread(background_task (*)())'
解决方法很简单,那就是使用大括号运算符来初始化对象:
std::thread t{background_task()};
- Effective STL
- https://www.cnblogs.com/wanger-sjtu/p/16876846.html