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 ]
由于析构函数很少被显式调用,我想知道是否有标准保证 在基础 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 ]