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

时间:2019-06-25 00:16:12

标签: c++ initialization language-lawyer c++17 access-levels

在下面的代码中,使用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;
}

1 个答案:

答案 0 :(得分:11)

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

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

(重点是我的)

  

没有用户提供的,继承的或显式的构造函数(明确允许使用默认或删除的构造函数)(自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()执行value-initialization,临时对象将由B的构造函数初始化,那么一切都很好。

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

  

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

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