为什么没有默认构造函数就无法编译?

时间:2018-12-16 22:09:57

标签: 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;
}

这样可以编译,我的反结果是 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

3 个答案:

答案 0 :(得分:87)

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);可以是{{1}的临时类型Boo的构造。 }作为num的构造函数的参数,也可以是声明Boo,并在声明符Boo num;周围带有额外的括号(声明符可能总是有)。如果两者都是有效的解释,则该标准要求编译器假设一个声明。

如果将其解析为声明,则num将调用默认构造函数(不带参数的构造函数),该构造函数既不是由您声明的,也不是隐式声明的(因为您声明了另一个构造函数)。因此该程序格式不正确。

Boo num;并不是问题,因为Boo(8);不能是变量的标识符(declarator-id),因此它被解析为使用{{创建临时8的调用1}}作为构造函数的参数,因此不会调用默认的构造函数(未声明),而是调用您手动定义的构造函数。

您可以通过将临时变量设为命名变量,例如使用Boo而不是8(因为不允许在声明符周围使用Boo{num};)来消除声明中的歧义。 Boo(num);,或将其作为操作数放在另一个表达式中,例如{}Boo temp(num);

请注意,如果默认构造函数可用,则声明的格式将正确,因为声明位于(Boo(num));的分支块范围内,而不是函数的块范围内,并且只会遮盖(void)Boo(num);在函数的参数列表中。

无论如何,将临时对象创建误用于应该是正常(成员)函数调用的东西似乎不是一个好主意。

仅在意图中创建一个临时类型并立即将其丢弃,或者如果意图是创建一个直接用作临时对象的临时类型,则可能发生这种特殊类型的最令人烦恼的分析,并在括号中使用单个非类型名称。初始化程序,例如if(实际上声明函数num的类型为Boo boo(Boo(num));的参数名为boo并返回num)。

通常不打算立即丢弃临时变量,并且可以使用大括号初始化或双括号(BooBooBoo boo{Boo(num)}而不是{{1} }。

如果Boo boo(Boo{num})不是类型名称,则它不能是声明,也不会出现问题。

我还要强调一点,Boo boo((Boo(num)));正在创建类型为Boo boo(Boo((num)));的新临时文件,即使在类范围和构造函数定义中也是如此。就像人们可能误认为的那样,它不是像通常的非静态成员函数那样使用调用者的Boo指针对构造函数的调用。无法在构造函数主体内以这种方式调用另一个构造函数。这只能在构造函数的成员初始化器列表中。


即使由于[stmt.ambig]/3而由于缺少构造函数而导致声明不正确的情况,也会发生这种情况:

  

消歧纯粹是句法上的;也就是说,   在这样的陈述中出现的名字,除了它们是否   是否输入类型名称,通常不会在其中使用或更改   消除歧义。

     

[...]

     

歧义消除是在解析之前进行的,并且被声明为声明歧义的语句可能是格式错误的声明。


已在编辑中修复:我忽略了所涉及的声明与函数参数的作用域不同,因此如果构造函数可用,则声明的格式正确。无论如何,在进行歧义消除时都不会考虑这一点。还扩展了一些细节。

答案 1 :(得分:33)

这被称为 最令人烦恼的解析 (The term was used by Scott Meyers in 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]

所以编译器看到的等同于

Boo num;

这是一个可变的脱位。您声明了一个名称为num的Boo变量,即使您要创建一个临时的Boo对象,也需要默认构造函数。 C ++标准要求您的情况下的编译器假定这是一个变量声明。您现在可能会说:“嘿,num是一个整数,请不要那样做。”但是,standard says

  

消歧纯粹是句法上的;也就是说,在这种陈述中出现的名称的含义,除了它们是否为类型名称之外,通常不会在歧义中使用或更改。   根据需要实例化类模板,以确定限定名称是否为类型名称。   歧义消除在解析之前进行,并且歧义为声明的声明可能是格式错误的声明。   如果在解析过程中,模板参数中的名称绑定与试用解析中的绑定不同,则程序格式错误。   无需诊断。   [注意:仅当在声明中更早声明名称时,才会发生这种情况。   —注   ]

所以没有办法。

对于Boo(8),这不会发生,因为解析器可以确定这不是declecle(8不是有效的标识符名称),并调用构造函数Boo(int)

顺便说一句:您可以使用括号将其消除歧义:

 if (rand() % num < 7)  (Boo(num));

或者我认为更好,请使用新的统一初始化语法

if (rand() % num < 7)  Boo{num};

然后将编译see herehere

答案 2 :(得分:1)

这是叮当警告

truct_init.cpp:11:11:错误:使用不同类型重新定义了'num':'Boo'       vs'int'