首页 > 解决方案 > 为什么没有默认构造函数就不能编译?

问题描述

我可以做这个:

#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

标签: c++constructorscopedefault-constructormost-vexing-parse

解决方案


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

如果它被解析为声明,那么Boo num;将调用默认构造函数(没有参数的构造函数),它不是由您声明或隐式声明的(因为您声明了另一个构造函数)。因此,该程序是格式错误的。

这不是问题Boo(8);,因为8不能是变量的标识符(declarator-id),所以它被解析为创建一个Boo临时的调用8作为构造函数的参数,因此不调用默认构造函数(未声明),但是您手动定义的那个。

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

请注意,如果默认构造函数可用,则声明将是格式良好的,因为它位于if的分支块范围内,而不是函数的块范围内,并且只会num在函数的参数列表中隐藏 。

在任何情况下,将临时对象创建用于应该是正常(成员)函数调用的东西似乎都不是一个好主意。

这种特殊类型的括号中只有一个非类型名称的最麻烦的解析只能发生,因为打算创建一个临时的并立即丢弃它,或者如果打算创建一个直接用作初始值设定项的临时,例如Boo boo(Boo(num));(实际上声明函数boo接受一个名为numtypeBoo并返回的参数Boo)。

通常不打算立即丢弃临时对象,并且可以使用大括号初始化或双括号(Boo boo{Boo(num)}, Boo boo(Boo{num})or Boo boo((Boo(num)));, but not Boo boo(Boo((num)));)来避免初始化案例。

如果Boo不是类型名称,则它不能是声明并且不会出现问题。

我还想强调的是,即使在类作用域和构造函数定义中,也会Boo(8);创建一个新的临时类型。正如人们可能错误地认为的那样,它不像通常的非静态成员函数那样Boo使用调用者的指针调用构造函数。this在构造函数体内不能以这种方式调用另一个构造函数。这仅在构造函数的成员初始值设定项列表中才有可能。


即使声明由于缺少构造函数而格式错误,也会发生这种情况,因为[stmt.ambig]/3

消歧纯粹是句法;也就是说,出现在这种陈述中的名称的含义,除了它们是否是类型名称之外,通常不会在消歧中使用或改变。

[...]

消歧先于解析,作为声明消歧的语句可能是格式错误的声明。


在编辑中修复:我忽略了有问题的声明与函数参数在不同的范围内,因此如果构造函数可用,则声明格式正确。在任何情况下,在消歧过程中都不会考虑这一点。还扩展了一些细节。


推荐阅读