“类型为'A'的临时类型具有受保护的析构函数”,但其类型为B

jtb*_*des 10 c++ initialization access-levels language-lawyer c++17

在以下代码中,使用Clang 8.0.0+进行编译,并-std=c++17使用创建派生类实例,将B{}给出error error: temporary of type 'A' has protected destructorA当临时类型具有类型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;
}
Run Code Online (Sandbox Code Playgroud)

son*_*yao 11

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

在C ++ 20之前,B(和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无法调用析构函数销毁类型为的直接构造的基础子对象A。(它不会抱怨protected构造函数,因为它也被汇总初始化绕开A了。)

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

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

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

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