In an std::vector<T>
the vector owns the allocated storage and it constructs T
s and destructs T
s. Regardless of T
's class hierarchy, std::vector<T>
knows that it has only created a T
and thus when .pop_back()
is called it only has to destroy a T
(not some derived class of T
). Take the following code:
#include <vector>
struct Bar {
virtual ~Bar() noexcept = default;
};
struct FooOpen : Bar {
int a;
};
struct FooFinal final : Bar {
int a;
};
void popEm(std::vector<FooOpen>& v) {
v.pop_back();
}
void popEm(std::vector<FooFinal>& v) {
v.pop_back();
}
https://godbolt.org/z/G5ceGe6rq
The PopEm
for FooFinal
simply just reduces the vector's size by 1 (element). This makes sense. But PopEm
for FooOpen
calls the virtual destructor that the class got by extending Bar
. Given that FooOpen
is not final, if a normal delete fooOpen
was called on a FooOpen*
pointer, it would need to do the virtual destructor, but in the case of std::vector
it knows that it only made a FooOpen
and no derived class of it was constructed. Therefore, couldn't std::vector<FooOpen>
treat the class as final and omit the call to the virtual destructor on the pop_back()
?
FooOpen
? The only possibility I can see is that a user may placement-new a derived object into the storage of aFooOpen
element, which would at the very least depend on a lot of unspecified behavior, but I feel like should be library undefined behavior in the first place. – Coquetstd::allocator_traits::destroy
is used to destroy the element, which forstd::allocator
as allocator simply means a (unqualified) destructor call. The standard library can detect and special case the container ifstd::allocator
is used (they already do that for optimization of trivially copyable types) and then always use a qualified destructor call instead ofallocator_traits::destroy
, which will enforce a static dispatch even if the class has a virtual destructor. – Coquetstd::vector
? What happens if you simply do{ volatile FooOpen x; }
? – Jobberstd::optional
knows that it is just aFooOpen
and doesn't bother calling the virtual dtor. Overall, I think in the vector pop code, it needs to doitem->FooOpen::~FooOpen()
instead ofitem->~FooOpen()
and thus it can call the more optimized dtor. – GurlFooOpen
asfinal
, since astd::vector<FooOpen>
can never contain an instance of a class derived fromFooOpen
- any add/copy of an instance of a derived class into the vector (e.g. bypush_back()
) will slice the object. Destroying each element will therefore call destructors ofFooOpen
then its bases (in that order). It doesn't even matter if the base destructor is virtual (since no object is destroyed via a pointer to base). How the compiler optimises those destructor calls (when it has visibility of them) is then a quality of implementation concern – CynthFooOpen
's destructor will be called. That is the whole point. It is impossible to store any other type but exactlyFooOpen
in the vector. The vector interface simply does not allow anything else. – Coquet