12

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

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;
}
4

1 回答 1

14

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

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

(强调我的)

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

然后

如果初始化子句的数量少于成员的数量and bases (since C++17)或初始化列表完全为空,则剩余成员由空列表and bases (since C++17)初始化by their default member initializers, if provided in the class definition, and otherwise (since C++14),按照通常的列表初始化规则(对非类类型执行值初始化和具有默认构造函数的非聚合类,以及聚合的聚合初始化)。

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

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

顺便说一句,从 C++20 开始,您的代码就可以正常工作。

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

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

于 2019-06-25T00:44:43.143 回答