In the following code, compiled with Clang 8.0.0+ and -std=c++17
, creating a derived class instance using B{}
gives an error error: temporary of type 'A' has protected destructor
. Why is A
appearing in this message when the temporary has type B
(and thus should have a public destructor)?
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;
}
This is a subtle issue of aggregate initialization before C++20.
Before C++20, B
(and A
) are aggregate types:
(emphasis mine)
no user-provided, inherited, or explicit constructors (explicitly defaulted or deleted constructors are allowed) (since C++17) (until C++20)
Then
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 membersand bases (since C++17)
are initializedby 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).
So B{}
constructs a temporary object via aggregate initialization, which will initialize the base subobject directly with empty list, i.e. perform aggregate initialization to construct the A
base subobject. Note that the constructor of B
is bypassed. The problem is that in such context, the protected
desctructor can't be called to destroy the directly constructed base subobject of type A
. (It doesn't complain about the protected
constructor because it's bypassed by the aggregate initialization of A
too.)
You can change it to foo(B());
to avoid aggregate initialization; B()
performs value-initialization, the temporary object will be initialized by B
's constructor, then anything is fine.
BTW since C++20 you code will work fine.
no user-declared or inherited constructors (since C++20)
B
(and A
) are not aggregate types again. B{}
performes list initialization, and then the temporary object is initialized by B
's constructor; the effect is just same as B()
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With