C++'s most vexing parse
直接看一个例子:
#include<iostream>
#include<thread>
#include <chrono>
using namespace std::chrono_literals;
class task {
public:
void operator() () {
std::cout << "task begin..." << std::endl;
std::this_thread::sleep_for(2s);
std::cout << "task done!" << std::endl;
}
};
int main() {
std::thread t(task());
t.join();
return 0;
}
这段代码很简单,在主线程(main函数)中起动一个子线程,并等待子线程执行完成。然而,上面代码编译会报错:
test.cpp: In function ‘int main()’:
test.cpp:18:7: error: request for member ‘join’ in ‘t’, which is of non-class type ‘std::thread(task (*)())’
18 | t.join();
| ^~~~
因为 t
根本就不是一个类类型的对象,而是一个函数,这里的 std::thread t(task())
并非创建一个 std::thread
对象并启动该线程执行,而是申明了一个函数,该函数的返回值类型为 std::thread
,函数名为 t
,参数为一个函数指针(返回值类型为 task,无函数名,参数为空)。这就是本文的主题:C++'s most vexing parse(C++最令人费解的解析)。Scott Meyers 在《Effective STL》中的 Item 6 有相关介绍。
在 C++ 中,加入我们要申明一个函数 f
,返回值类型为 int
,参数类型为 double
,则以下 3 种都是合法的申明方式:
int f(double d);
int f(double (d));
int f(double);
第二个申明,对行参加上圆括号,编译器会认为是冗余的并忽略。第三个省略了行参名。
如果我们想申明一个函数,并且其参数是一个函数指针,下面 3 种写法都是合法的:
int g(double (*pf)());
int g(double pf());
int g(double ());
第二个申明中行参省去指针类型也是合法的,第三个申明中行参直接省去了函数名也是合法的。
更普遍地,对于下面的语句:
T1 name(T2());
T1 name1(T2(name2));
C++都会将其视为函数申明:
- 对于第一个语句,C++将其视为:返回值类型为
T1
名为name
的函数(参数类型为指向返回值类型为T2
,参数为空的函数的指针)。 - 对于第二个语句,C++将其视为:
T1 name1(T2 name2);
显然也是一个函数申明。
再看本文开头给出的例子,就不难理解编译报错的原因了。解决办法也比较多,比如:
std::thread t((task()));
std::thread t{task()};
task f
std::thread t(f);
至此,本文结束,希望对你有所帮助。
参考:
- Scott Meyers 《Effective STL》
- http://eel.is/c++draft/dcl.ambig.res
- https://www.fluentcpp.com/2018/01/30/most-vexing-parse/