提问人:Zebrafish 提问时间:12/17/2018 最后编辑:cpplearnerZebrafish 更新时间:4/27/2019 访问量:6064
为什么没有默认构造函数就不能编译?
Why won't this compile without a default constructor?
问:
我可以这样做:
#include <iostream>
int counter;
int main()
{
struct Boo
{
Boo(int num)
{
++counter;
if (rand() % num < 7) Boo(8);
}
};
Boo(8);
return 0;
}
这将编译良好,我的计数器结果是 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;
}
如何在第二个示例中调用默认构造函数,但在第一个示例中不调用?这是我在 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
答:
这被称为最令人烦恼的解析(Scott Meyers 在 Effective STL 中使用了该术语)。
Boo(num)
不调用构造函数,也不创建临时构造函数。Clang 给出了一个很好的警告(即使有正确的名称 Wvexing-parse):
<source>:12:38: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named 'num' [-Wvexing-parse]
所以编译器看到的等价于
Boo num;
这是一个可变的衰减。您声明了一个名为 num 的 Boo 变量,该变量需要默认构造函数,即使您想创建一个临时的 Boo 对象也是如此。c++ 标准要求编译器在您的案例中假定这是一个变量声明。你现在可能会说:“嘿,num 是一个 int,不要那样做。但是,该标准说:
消除歧义纯粹是句法上的;也就是说,在这种语句中出现的名称的含义,除了它们是否是类型名称之外,通常不会在消歧中使用或改变。 根据需要实例化类模板,以确定限定名称是否为 type-name。 消除歧义先于分析,作为声明消除歧义的语句可能是格式不正确的声明。 如果在分析过程中,模板参数中的名称的绑定方式与试验分析期间的绑定方式不同,则程序的格式不正确。 无需诊断。 [ 注意:仅当名称在声明的前面声明时,才会发生这种情况。 — 尾注 ]
所以没有办法摆脱这种情况。
因为这不会发生,因为解析器可以确定这不是一个降级(8 不是有效的标识符名称)并调用构造函数 .Boo(8)
Boo(int)
顺便一提:您可以使用括号来消除歧义:
if (rand() % num < 7) (Boo(num));
或者在我看来,更好的是,使用新的统一初始化语法
if (rand() % num < 7) Boo{num};
评论
num
Clang 给出以下警告消息:
<source>:12:16: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named 'num' [-Wvexing-parse]
Boo(num); // No default constructor
^~~~~
这是一个最棘手的解析问题。因为是类类型的名称而不是类型名称,所以可以是带有 的构造函数的临时类型的构造,也可以是声明符周围带有额外括号的声明(声明器可能始终具有括号)。如果两者都是有效的解释,则标准要求编译器承担声明。Boo
num
Boo(num);
Boo
num
Boo
Boo num;
num
如果它被解析为声明,则将调用默认构造函数(没有参数的构造函数),它既不是由您声明的,也不是隐式声明的(因为您声明了另一个构造函数)。因此,程序格式不正确。Boo num;
这不是 的问题,因为不能是变量的标识符 (declarator-id),因此它被解析为构造函数创建一个临时的 with as 参数的调用,从而不调用默认构造函数(未声明),而是调用您手动定义的构造函数。Boo(8);
8
Boo
8
您可以通过使用 instead of (因为不允许在声明符周围)、将 temporary 设置为命名变量(例如 )或将其作为操作数放在另一个表达式中(例如 、 等)来消除声明中的歧义。Boo{num};
Boo(num);
{}
Boo temp(num);
(Boo(num));
(void)Boo(num);
请注意,如果默认构造函数可用,则声明的格式将正确,因为它位于 的分支块作用域内,而不是函数的块作用域内,并且只会在函数的参数列表中隐藏 。if
num
无论如何,将临时对象创建误用于本应是正常(成员)函数调用的内容似乎不是一个好主意。
这种特殊类型的最令人烦恼的解析,括号中只有一个非类型名称,只能发生,因为意图是创建一个临时的并立即丢弃它,或者如果意图是创建一个直接用作初始值设定项的临时,例如 (实际上声明函数取一个以 type 命名的参数并返回 )。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:
消除歧义纯粹是句法上的;也就是说,含义 在这样的陈述中出现的名字,超出了它们是否是 无论是否 type-names,通常不会在 澄清。
[...]
消除歧义先于分析,作为声明消除歧义的语句可能是格式不正确的声明。
在编辑中修复:我忽略了所讨论的声明与函数参数的范围不同,因此如果构造函数可用,则声明的格式正确。在任何情况下,在消除歧义时都不会考虑这一点。还扩展了一些细节。
评论
Boo(...)
int(x)=5;
这是叮当声警告
truct_init.cpp:11:11:错误:使用不同的类型重新定义“num”:“Boo” 与 'int'
上一个:如何将列表的值与其索引的幂相加
评论