Suppose we have the following class template:
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;
};
(Please ignore the duplication in the move constructor and move assignment operator for the moment)
This class is meant to act as a base class for OpenGL object wrappers. Following is an example use:
class VertexBuffer : public Object<VertexBuffer>
{
public:
VertexBuffer()
{
glGenBuffers(1, &m_Id);
...
}
void Release()
{
glDeleteBuffers(1, &m_Id);
}
};
The Object<T> class template is supposed to take care of the bookkeeping.
The reason for doing this is that the pattern in the Object<T> class is repeated the exact same way for (almost) every OpenGL object wrapper that might be written. The only difference is the creation and deletion of the objects which is handled by the constructor and the Release() function in this example.
Now the question is whether this (Object<T>::~Object() to be specific) taps into UB land? Undefined Behavior Sanitizer doesn't report any errors but I've never done this, so I though of asking people with more experience to make sure.
Short answer: Yes, this is undefined behaviour, don't do that.
Long answer:
The destruction of VertexBuffer invokes first ~VertexBuffer() and then invokes ~Object<VertexBuffer>() afterwards. When ~Object<VertexBuffer>() is invoked the VertexBuffer "part" of the object has already been destroyed, i.e. you are now doing an illegal downcast via static_cast (the remaining valid part of the object is a Object<VertexBuffer>, not a VertexBuffer).
And undefined behaviour permits the compiler to do ANYTHING - it might even (appear to) work, only to suddenly stop working (or only work when you build in Debug mode, but not when in Release). So, for your own sake, please don't do that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With