繁体   English   中英

在基类'Destructor中调用派生类'(非虚)function

[英]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>的“事物”,其中TCrtp类型,那么该“事物”很可能是一个模板。

因此,与其持有一个Object<T> ,不如持有一个T ,它是继承自Object<T>的完整类型。 如果它被破坏,它将自动调用T::~T()

此外,也许您想从Object<T>执行privateprotected的 inheritance 以阻止用户对 Crtp 类型进行切片。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM