原文::http://zkt.name/c-s-most-vexing-parse/
C++'s most vexing parse
最近同事在使用C++的时候遇到一个诡异的问题,初始化一个对象的时候构造函数没有被调用。类似的代码如下:
#include <iostream>
class A {
public:
A(const std::string& name){
std::cout << name << std::endl;
}
};
int main()
{
char szTmp[] = "Hello";
A a(std::string(szTmp));
return 0;
}
这段代码非常简单,意图就是构造一个std::string
匿名对象,然后传递给类A的构造函数,构造函数输出这个字符串。但是这段代码什么都没输出,说明构造函数没有被执行。这个奇怪的结果让我很好奇。
经过一番资料查阅,原来A a(std::string(szTmp));
这段代码被解析成了一个函数名为a有一个std::string参数szTmp并返回A类型的函数声明。这在Scott Meyers的《Effective STL》有做解释,并把这个问题称为C++'s most vexing parse,本文也用了这个标题,翻译为C++最令人费解的解析。
在C++中,以下三种写法都声明了同一个函数
int f(double d); //声明接受一个double参数d,返回值为int类型的函数
int f(double (d));//效果一样,参数名外的括号会被忽略
int f(double);//直接省略参数名
类似的,以下三种写法都声明的函数也相同
int g(double (*pf)()); //声明接受一个无参数返回类型为double的函数指针pf参数,返回值为int类型的函数
int g(double pf());//效果一样,pf是隐式函数指针
int g(double ());//直接省略参数名
前面代码中的A a(std::string(szTmp));
其实就跟函数f
的第二种声明方式,szTmp两边的括号被忽略。然后被解析成一个函数声明。还有一种情况,如果这段这段代码改成A a();
,也不会调用A的默认构造函数,同样会被解析成函数声明。 这确实是一个违反直觉的解析方式,所以在C++11中,针对这种情况,有提出解决方案。
Scott在书中有提到一种解决方法,就是把整个匿名对象用括号括起来,就像这样A a((std::string(szTmp)));
。更好地做法还是避免写这样的代码,而是先在外面初始化一个std::string类型的变量,然后再传给构造函数。或者直接通过隐式转换A a(szTmp);
来创建对象。
在C++11中,使用Uniform initialization可以处理这种歧义,
Uniform initialization syntax
Using the new uniform initialization syntax introduced in C++11 solves this issue.
The problematic code is then unambiguous when braces are used:
TimeKeeper time_keeper{Timer{}};
使用新的语法可以这样写A a{std::string(szTmp)};