类型为B的临时变量拥有一个受保护的析构函数,但其类型应为A。

14
在使用Clang 8.0.0+和 -std=c++17编译的以下代码中,使用B{}创建派生类实例会出现错误error: temporary of type 'A' has protected destructor。为什么该临时对象的类型是B(因此应具有公共析构函数),但错误消息中出现了A

https://godbolt.org/z/uOzwYa

class A {
protected:
    A() = default;
    ~A() = default;
};

class B : public A {
// can also omit these 3 lines with the same result
public:
    B() = default;
    ~B() = default;
};

void foo(const B&) {}

int main() {

    // error: temporary of type 'A' has protected destructor
    foo(B{});
    //    ^

    return 0;
}

这段代码在我的clang编译器上没有错误。你用的是哪个版本的clang? - Cruz Jean
如果您运行 clang --version,会显示什么内容? - Cruz Jean
错误出现在clang 8.0.0中:https://godbolt.org/z/uOzwYa(6或7没有错误) - jtbandes
这很奇怪。我在使用苹果LLVM 10.0.1时也遇到了同样的问题。 - Eljay
1
很可能是因为B继承了拥有受保护析构函数的A - user10957435
1
类似问题 - Barry
1个回答

18

这是关于C++20之前的聚合初始化的一个微妙问题。

在C++20之前,B(和 A)是聚合类型

(强调我的)

没有用户提供的、继承的或显式构造函数(自 C++17 起允许显式默认或删除构造函数)(直到 C++20)

然后

如果初始化器数量小于成员变量和基类的数量(自 C++17 起包括基类)或初始化列表为空,则按照通常的列表初始化规则初始化剩余的成员变量和基类(自 C++17 起包括基类),如果类定义中提供了默认成员初始化器,则使用默认成员初始化器进行初始化,否则(自 C++14 起)用空列表进行初始化,对于非类类型和带有默认构造函数的非聚合类执行值初始化,并对聚合类型执行聚合初始化。

B{} 通过聚合初始化构造了一个临时对象,直接使用空列表来初始化基类子对象,即对 A 基类子对象执行聚合初始化。需要注意的是,B 的构造函数被绕过。问题在于,在这种情况下,protected 析构函数无法调用以销毁类型为 A 的直接构造基类子对象。 (它不会因为在 A 的聚合初始化中也被绕过而抱怨 protected 构造函数。)

您可以将其更改为 foo(B());,以避免聚合初始化。使用 B() 执行 值初始化,临时对象将由 B 的构造函数初始化,然后一切都好了。

顺便说一下,自 C++20 起,您的代码将正常工作。

没有用户声明或继承的构造函数(自 C++20 起)

B(和 A)再次不是聚合类型。 B{} 执行列表初始化,然后临时对象由 B 的构造函数初始化;效果与 B() 相同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接