Zeb*_*ish 70 c++ constructor scope default-constructor most-vexing-parse
我可以做这个:
#include <iostream>
int counter;
int main()
{
struct Boo
{
Boo(int num)
{
++counter;
if (rand() % num < 7) Boo(8);
}
};
Boo(8);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这将编译正常,我的计数器结果是21.但是,当我尝试创建Boo传递构造函数参数而不是整数文字的对象时,我得到一个编译错误:
#include <iostream>
int counter;
int main()
{
struct Boo
{
Boo(int num)
{
++counter;
if (rand() % num < 7) Boo(num); // No default constructor
// exists for Boo
}
};
Boo(8);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
如何在第二个示例中调用默认构造函数,而不是在第一个示例中调用?这是我在Visual Studio 2017上遇到的错误.
在线C++编译器onlineGDB我得到错误:
error: no matching function for call to ‘main()::Boo::Boo()’
if (rand() % num < 7) Boo(num);
^
note: candidate expects 1 argument, 0 provided
Run Code Online (Sandbox Code Playgroud)
use*_*163 86
Clang给出了这条警告信息:
<source>:12:16: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named 'num' [-Wvexing-parse]
Boo(num); // No default constructor
^~~~~
Run Code Online (Sandbox Code Playgroud)
这是一个最令人烦恼的解析问题.因为Boo是类类型的名称而num不是类型名称,Boo(num);所以可以构造Boo带有num作为Boo构造函数的参数的临时类型,也可以是声明Boo num;符周围带有额外括号的声明num(声明符可能始终具有).如果两者都是有效的解释,则标准要求编译器采用声明.
如果它被解析为声明,那么Boo num;将调用默认构造函数(没有参数的构造函数),这不是由您或隐式声明(因为您声明了另一个构造函数).因此,该计划是不正确的.
这不是问题Boo(8);,因为8它不能是变量的标识符(declarator-id),所以它被解析为一个调用Boo,8为构造函数创建一个临时的with 参数,从而不调用默认的构造函数(未声明),但是你手动定义的那个.
您可以从一个宣言,或者使用本歧义Boo{num};,而不是Boo(num);(因为{}周围的声明符是不允许的),通过将一个临时命名的变量,例如Boo temp(num);,或者将它作为一个操作数的另一种表达方式,例如(Boo(num));,(void)Boo(num);等等.
请注意,如果默认构造函数是可用的,声明将是格式良好的,因为它位于if分支块范围内而不是函数的块范围内,并且只会影响num函数的参数列表.
在任何情况下,滥用临时对象创建应该是正常(成员)函数调用的东西似乎不是一个好主意.
这种特殊类型的最令人烦恼的解析在括号中只有一个非类型名称只能发生,因为打算创建一个临时的并立即丢弃它,或者如果打算创建一个临时直接用作初始化程序,例如Boo boo(Boo(num));(实际上声明函数boo采用num以类型命名Boo并返回的参数Boo).
丢弃临时对象立即通常不打算,并且可以使用支架初始化或双括号避免初始化的情况下(Boo boo{Boo(num)},Boo boo(Boo{num})或Boo boo((Boo(num)));,但不是Boo boo(Boo((num)));).
如果Boo不是类型名称,则它不能是声明,也不会出现问题.
我还想强调的Boo(8);是Boo,即使在类范围和构造函数定义中,也要创建一个新的临时类型.正如人们可能错误地认为的this那样,对于通常的非静态成员函数,使用调用者指针调用构造函数并不是这样.在构造函数体内以这种方式调用另一个构造函数是不可能的.这只能在构造函数的成员初始值设定项列表中使用.
即使由于缺少构造函数而导致声明格式不正确,也会发生这种情况,因为[stmt.ambig]/3:
消歧纯粹是语法上的; 也就是说,在这种陈述中出现的名称的含义,除了它们是否是类型名称之外,通常不会被消除歧义使用或改变.
[...]
在解析之前消歧,并且消除歧义作为声明的声明可能是不正确的声明.
在编辑中修复:我忽略了有问题的声明在与函数参数和声明不同的范围内,因此如果构造函数可用,则声明格式正确.在任何情况下,在消除歧义时都不考虑这一点.还扩展了一些细节.
use*_*999 33
这被称为最令人烦恼的解析 (该术语由Scott Meyers在Effective STL中使用).
Boo(num)不会调用构造函数也不会创建临时的.Clang给出了一个很好的警告(即使使用正确的名称W vexing-parse):
<source>:12:38: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named 'num' [-Wvexing-parse]
Run Code Online (Sandbox Code Playgroud)
所以编译器看到的是等价的
Boo num;
Run Code Online (Sandbox Code Playgroud)
这是一个可变的decleration.你声明了一个名为num的Boo变量,它需要默认的构造函数,即使你想创建一个临时的Boo对象.c ++标准要求您的情况下的编译器假设这是一个变量声明.你现在可能会说:"嘿,num是一个int,不要这样做." 但是,标准说:
消歧纯粹是语法上的; 也就是说,在这种陈述中出现的名称的含义,除了它们是否是类型名称之外,通常不会被消除歧义使用或改变.根据需要实例化类模板,以确定限定名称是否为类型名称.在解析之前消歧,并且消除歧义作为声明的声明可能是不正确的声明.如果在解析期间,模板参数中的名称的绑定方式与试验解析期间绑定的名称不同,则程序格式不正确.无需诊断.[注意:只有在声明中声明名称时才会出现这种情况. - 结束说明]
所以没有办法解决这个问题.
因为Boo(8)这不可能发生,因为解析器可以确定这不是一个decleration(8不是有效的标识符名称)并调用构造函数Boo(int).
顺便说一下:您可以使用括号括起来消除歧义:
if (rand() % num < 7) (Boo(num));
Run Code Online (Sandbox Code Playgroud)
或者在我看来更好,使用新的统一初始化语法
if (rand() % num < 7) Boo{num};
Run Code Online (Sandbox Code Playgroud)