'polyBasePtr->~PolyBase() : is this really a virtual call?
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;
}
Solution 1:[1]
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();
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | HolyBlackCat |
