polyBasePtr->~PolyBase() :这真的是虚拟调用吗?

polyBasePtr->~PolyBase() : is this really a virtual call?

由于析构函数很少被显式调用,我想知道是否有标准保证 在基础 class 指针上显式调用虚拟析构函数 将调用最派生对象的 dtor。 在我检查过的情况下,这确实是 gcc、clang 和 msvc 中发生的情况, 但我还是想知道真的有保障

老实说,我可能永远不会问这个问题,如果名字 每个 class 中的析构函数只是 destructor()。然而,调用

basePtr->~Base();

有点像

basePtr->Base::virtual_fcn();

这不是虚拟呼叫。相比之下,通过

调用最派生对象的 dtor 的通常方法
delete basePtr;

不向编译器提供提示(也许)我们正在请求基础 class 版本。

顺便说一句,这不是一个纯粹的学术问题。我想要一个容器,它可以存储一个 class 的单个对象,该对象是从具有虚拟 dtor 的给定基派生的,最大大小不超过某个。如果 basePtr->~Base() 是虚拟调用,则执行这样的 容器非常简单,仅出于完整性考虑,如下所示(类似于 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;

}

是的,这是一个虚拟析构函数调用。 non-virtual 呼叫将是 basePtr->Base::~Base();.

测试方法如下:

#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();
}

[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 ]