C++‘s most vexing parse

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/
  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值