"temporary of type 'A' has protected destructor",但它的类型是B

"temporary of type 'A' has protected destructor", but its type is B

在以下代码中,使用 Clang 8.0.0+ 和 -std=c++17 编译,使用 B{} 创建派生的 class 实例会出现错误 error: temporary of type 'A' has protected destructor。当临时对象的类型为 B(因此应该有一个 public 析构函数)时,为什么 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;
}

这是 C++20 之前 aggregate initialization 的一个微妙问题。

在 C++20 之前,B(和 A)是 aggregate types:

(强调我的)

no user-provided, inherited, or explicit constructors (explicitly defaulted or deleted constructors are allowed) (since C++17) (until C++20)

然后

If the number of initializer clauses is less than the number of members and bases (since C++17) or initializer list is completely empty, the remaining members and bases (since C++17) are initialized by their default member initializers, if provided in the class definition, and otherwise (since C++14) by empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates).

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

可以改为foo(B());,避免聚合初始化; B()执行value-initialization,临时对象会被B的构造函数初始化,那就没问题了。

顺便说一句,自 C++20 以来,您的代码可以正常工作。

no user-declared or inherited constructors (since C++20)

B(和A)不再是聚合类型。 B{}执行list initialization,然后临时对象被B的构造函数初始化;效果与 B().

相同