c++ - 为什么没有默认构造函数就不能编译?

标签 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

最佳答案

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 的分支 block 范围内,而不是函数的 block 范围内,并且会简单地隐藏 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 :

The disambiguation is purely syntactic; that is, the meaning of the names occurring in such a statement, beyond whether they are type-names or not, is not generally used in or changed by the disambiguation.

[...]

Disambiguation precedes parsing, and a statement disambiguated as a declaration may be an ill-formed declaration.


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

关于c++ - 为什么没有默认构造函数就不能编译?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53806896/

相关文章:

javascript - Vue 范围 : how to delay handling of @mouseover

c++ - long long 可能不存储整数?

c++ - 反射格雷码到二进制转换中的魔数(Magic Number)

c++ - ostringstream 和结束

c++ - 如何在标准构造函数之外设置 boost::uniform_int_distribution 的参数?

java - 使用不可变的享元跳过多余的验证

c++ - 确定C++中类构造函数的参数个数和类型?

c++ - 使用AKAZE时opencv 3.0下和windows 7下mingw下的异常

perl - 你如何在没有 eval 的情况下本地化一些遗留的全局变量?

c++ - 当我为我的简单类数组重载赋值运算符时,我得到了我期望的错误答案