如果没有默认构造函数,为什么不编译?

问题描述 投票:71回答:3

我可以做这个:

#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++ constructor scope default-constructor most-vexing-parse
3个回答
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);可以是Boo类型的临时构造,numBoo的构造函数的参数,或者它可以是声明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采用名为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

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

[...]

在解析之前消歧,并且消除歧义作为声明的声明可能是不正确的声明。


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


33
投票

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

这是一个可变的decleration。你声明了一个名为num的Boo变量,它需要默认的构造函数,即使你想创建一个临时的Boo对象。 c ++标准要求您的情况下的编译器假设这是一个变量声明。你现在可能会说:“嘿,num是一个int,不要这样做。”但是,standard says

消歧纯粹是语法上的;也就是说,在这种陈述中出现的名称的含义,除了它们是否是类型名称之外,通常不会被消除歧义使用或改变。根据需要实例化类模板,以确定限定名称是否为类型名称。在解析之前消歧,并且消除歧义作为声明的声明可能是不正确的声明。如果在解析期间,模板参数中的名称的绑定方式与试验解析期间绑定的名称不同,则程序格式不正确。无需诊断。 [注意:只有在声明中声明名称时才会出现这种情况。 - 结束说明]

所以没有办法解决这个问题。

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

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

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

或者在我看来更好,使用新的统一初始化语法

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

然后将编译see herehere


1
投票

这是铿锵警告

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

© www.soinside.com 2019 - 2024. All rights reserved.