Since destructors are rarely called explicitly, I wonder if standard guarantees that calling explicitly a virtual destructor on a base class pointer will invoke a dtor of the most derived object. In cases I checked, this is indeed what happens in gcc, clang and msvc, but I would still like to know that it is really guaranteed.
To be honest, I would probably never asked this question, if the name of
destructor was simply destructor()
in every class. However, the call
basePtr->~Base();
looks a bit similar to
basePtr->Base::virtual_fcn();
which is not a virtual call. By contrast, usual way of invoking a dtor of the most derived object, through
delete basePtr;
does not provide a hint to a compiler that (maybe) we are requesting the base class version.
BTW, this is not a purely academic question. I want to have a container that can store a single object of a class derived from a given base with virtual dtor, up to some maximal size. If basePtr->~Base()
is a virtual call, implementation of such a
container is pretty easy and, only for completeness, shown below (sort of modeled after std::optional
).
#include<cstddef>
#include<type_traits>
#include<new>
#include<utility>
template <typename PolyBaseT, std::size_t MaxSize>
class SubClassContainer{
static_assert ( std::is_class_v<PolyBaseT> );
static_assert ( std::has_virtual_destructor_v<PolyBaseT> );
static_assert ( sizeof(PolyBaseT) <= MaxSize );
public:
SubClassContainer() noexcept :m_basePtr(nullptr) {}
void reset() noexcept { if(m_basePtr) {m_basePtr->~PolyBaseT(); m_basePtr=nullptr;} }
~SubClassContainer() noexcept { reset(); }
template<typename DerivedT, typename ...ArgsT>
DerivedT* emplace(ArgsT&& ...args)
{ static_assert ( std::is_convertible_v<DerivedT*,PolyBaseT*> );
static_assert ( std::is_constructible_v<DerivedT,decltype(std::forward<ArgsT>(args))...> );
static_assert ( sizeof (DerivedT) <= MaxSize );
static_assert ( alignof(DerivedT) <= alignof (std::max_align_t) );
reset();
auto result = ::new (static_cast<void*>(&m_storage[0]) ) DerivedT(std::forward<ArgsT>(args)... );
m_basePtr=result;
return result;
}
explicit operator bool () const noexcept {return m_basePtr;}
const PolyBaseT * operator->() const noexcept {return m_basePtr;}
PolyBaseT * operator->() noexcept {return m_basePtr;}
SubClassContainer(const SubClassContainer&) = delete;
SubClassContainer(SubClassContainer&&) = delete;
SubClassContainer& operator=(const SubClassContainer&) = delete;
SubClassContainer& operator=(SubClassContainer&&) = delete;
private:
alignas(std::max_align_t) unsigned char m_storage[MaxSize];
PolyBaseT *m_basePtr ;
};
#include <iostream>
struct B { virtual void id(){} virtual ~B() noexcept = default;};
struct D1:B{ virtual ~D1() {std::cout << __func__ <<std::endl;} };
struct D2:B{ virtual ~D2() {std::cout << __func__ <<std::endl;} };
struct D3:B{ D3(int) {}
D3(double&&) {}
virtual ~D3() {std::cout << __func__ <<std::endl;}
};
int main()
{
SubClassContainer<B,128> der;
der.emplace<D1>();
std::cout << "about to reset: " << std::endl;
der.emplace<D2>();
std::cout << "about to reset:" << std::endl;
der.emplace<D3>(7);
std::cout << "about to reset [implicitely]" << std::endl;
}
CodePudding user response:
[class.dtor]/14 In an explicit destructor call, the destructor is specified by a
~
followed by a type-name or decltype-specifier that denotes the destructor’s class type. The invocation of a destructor is subject to the usual rules for member functions (12.2.1)
[ Example:struct B { virtual ~B() { } }; struct D : B { ~D() { } }; D D_object; B* B_ptr = &D_object; void f() { D_object.B::~B(); // calls B’s destructor B_ptr->~B(); // calls D’s destructor }
—end example ]
CodePudding user response:
Yes, it's a virtual destructor call. A non-virtual call would be basePtr->Base::~Base();
.
Here's how you test it:
#include <iostream>
struct A
{
virtual ~A()
{
std::cout << "~A()\n";
}
};
struct B : A
{
virtual ~B()
{
std::cout << "~B()\n";
}
};
int main()
{
A *p = new B;
p->~A();
}