[英]Is it safe to call a non-virtual base class member function from the base class destructor using a derived class pointer?
[英]Call a derived class' (non-virtual) function in the base class' Destructor
假设我们有以下 class 模板:
template<typename T>
class Object
{
public:
Object() = default;
Object(const Object&) = delete;
Object(Object&& other) noexcept
{
if (this != &other)
{
static_cast<T*>(this)->Release();
m_Id = std::exchange(other.m_Id, 0);
}
}
auto operator=(const Object&) = delete;
Object& operator=(Object&& other) noexcept
{
if (this != &other) {
static_cast<T*>(this)->Release();
m_Id = std::exchange(other.m_Id, 0);
}
return *this;
}
~Object()
{
static_cast<T*>(this)->Release();
m_Id = 0;
}
protected:
std::uint32_t m_Id;
};
(请暂时忽略移动构造函数和移动赋值运算符中的重复)
此 class 旨在充当 OpenGL ZA8CFDE6331BD59EB2AC96F8911C4Bpers666Z 包装的基础 class。 以下是使用示例:
class VertexBuffer : public Object<VertexBuffer>
{
public:
VertexBuffer()
{
glGenBuffers(1, &m_Id);
...
}
void Release()
{
glDeleteBuffers(1, &m_Id);
}
};
Object<T>
class 模板应该负责簿记。
这样做的原因是Object<T>
class 中的模式以完全相同的方式重复(几乎)每个可能编写的 OpenGL object 包装器。 唯一的区别是在这个例子中由构造函数和Release()
function 处理的对象的创建和删除。
现在的问题是这个(具体来说是Object<T>::~Object()
是否进入了 UB 领域? Undefined Behavior Sanitizer 不会报告任何错误,但我从未这样做过,所以我想请有更多经验的人来确定。
不要那样做。 这将导致未定义的行为。
相反,将模板 class 实现为派生的 class,如下例所示。
class BufferGrandBase {
protected:
GLuint id;
};
template<class B>
class Buffer : public B {
public:
Buffer() {
B::Create();
}
~Buffer() {
B::Destroy();
}
};
class VertexBufferBase : public BufferGrandBase {
public:
void Create() { glGenBuffers(1, &id); }
void Destroy() { glDeleteBuffers(1, &id); }
};
typedef Buffer<VertexBufferBase> VertexBuffer;
这种模式还将简化构造函数和运算符的实现。
简短的回答:是的,这是未定义的行为,不要那样做。
长答案:
VertexBuffer
的销毁首先调用~VertexBuffer()
,然后调用~Object<VertexBuffer>()
。 当~Object<VertexBuffer>()
被调用时VertexBuffer
的 VertexBuffer “部分”已经被破坏,即您现在正在通过static_cast
进行非法向下转换( object 的剩余有效部分是Object<VertexBuffer>
,而不是VertexBuffer
)。
未定义的行为允许编译器做任何事情 - 它甚至可能(似乎)工作,只是突然停止工作(或仅在您以调试模式构建时工作,但在发布时不工作)。 所以,为了你自己,请不要那样做。
这与带有自定义删除器的std::unique_ptr<>
的 model 接近。 但它不太适合,因为std::unique_ptr
必须保存一个指针,而这种情况需要一个 integer 句柄类型。
所以这里是一个简短的例子。 一般的好建议是更喜欢包含而不是 inheritance,所以我特意将助手 object 放在拥有的 class 中。
我在这里真正想证明的是,除了实例的调用成员之外,还有许多方法可以自定义templates
中的行为。
#include <iostream>
#include <memory>
typedef size_t gd_size;
typedef int gd_handle;
void gen_buffers(gd_size size,gd_handle*buffs);
void del_buffers(gd_size size,gd_handle*buffs);
typedef void (*gd_func)(gd_size,gd_handle*buff);
template<gd_func gfunc,gd_func dfunc> class GenDel{
public:
GenDel(){gfunc(1,&buff);}
~GenDel(){dfunc(1,&buff);}
int get()const{return buff;}
private:
int buff;
GenDel(const GenDel&)=delete;
};
class BufferHolder{
public:
BufferHolder(){}
void do_thing() const{
std::cout<<"using "<<buffer.get()<<'\n';
}
private:
GenDel<gen_buffers,del_buffers> buffer;
};
int main() {
BufferHolder b;
BufferHolder c;
b.do_thing();
return 0;
}
int seed{0};
void gen_buffers(gd_size size,gd_handle*buffs){
for(size_t i{0};i<size;++i){
buffs[i]=++seed;
std::cout << "generated "<< buffs[i] << '\n';
}
}
void del_buffers(gd_size size,gd_handle*buffs){
for(gd_size i{0};i<size;++i){
std::cout << "deleted "<< buffs[i] << '\n';
}
}
如果您有一个包含Object<T>
的“事物”,其中T
是Crtp类型,那么该“事物”很可能是一个模板。
因此,与其持有一个Object<T>
,不如持有一个T
,它是继承自Object<T>
的完整类型。 如果它被破坏,它将自动调用T::~T()
。
此外,也许您想从Object<T>
执行private
或protected
的 inheritance 以阻止用户对 Crtp 类型进行切片。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.