request-free-img

C++中最烦人的解析 —— Most Vexing Parse 完全解析

C++中最烦人的解析 —— Most Vexing Parse 完全解析

如果你问我最烦C++什么?

我的回答一定是:most vexing parse(最烦人的解析)

它到底是什么?

“Most Vexing Parse”是 C++ 社区对C++中一种经典语法歧义的称呼,它被 Scott Meyers 在《Effective C++》中正式命名。

先来看一段“天真”的代码

#include <iostream>

class Widget {
public:
    Widget() {
        std::cout << "Widget 默认构造函数被调用!" << std::endl;
    }
    ~Widget() {
        std::cout << "Widget 析构函数被调用!" << std::endl;
    }
};

int main() {
    Widget w();
    return 0;
}

我们想要做什么?

在main函数中,我想创建一个Widget类对象w,并使用默认构造函数初始化。

于是我们很自然地写下了这行看似没问题的代码:

Widget w();

运行结果却让人崩溃

构造函数没被调用,析构函数也没被调用,什么都没输出!

对象呢?对象去哪儿了???

真相:最烦人的解析(Most Vexing Parse)

核心问题出在这一行代码上。

C++ 中有一条非常重要的规则,叫做“优先函数声明规则”(as-if rule / declaration优先):

任何可以被解释为函数声明的东西,编译器都会优先把它解释为函数声明。
这是一条铁律。

于是 Widget w(); 在编译器眼里根本不是创建一个对象,而是:

// 这是一个函数声明!
// 返回类型:Widget
// 函数名:w
// 参数列表:空参数
// (在函数作用域内声明了一个函数,啥也没干)
Widget w();

这个坑其实很常见

相信大多数C++程序员都曾经踩过类似的雷:

std::ifstream ifs("file.txt");

你以为打开了文件,其实你只是声明了一个函数 ifs,返回类型是 std::ifstream,接受一个 const char* 参数……

文件?根本没打开!

那到底该怎么正确创建对象?

最简单、最清晰、永远不会出错的方式:

Widget w;

从C++11开始,强烈推荐使用统一初始化语法(大括号初始化),意图更明确:

Widget w{};

或者更直白一点:

Widget w = Widget();

总结一句话

在C++里,当你想声明一个对象却不小心写成了函数声明时,
编译器永远会选择相信你是在声明函数。

最后问一句:

你觉得这样的C++……到底烦不烦人?


更多问题探讨,请关注公众号:程序员角