I am designing a memory allocator that is able to move objects during their lifetime. To support this it requires use of IndirectPointer that points to a control block. This control block points to the true location of the object and is updated if the memory allocator wants to move an objects.
This creates a problem when you want an IndirectPointer to a base class of the allocated type, as the pointer value in the memory block points to the derived class. I've come up with the following solution, by checking the offset between the base and derived pointers, storing it, and adjusting the memory block pointer by this offset.
Is this approach defined behavior? Specifically, is storing an offset along with a pointer defined behavior including any potential edge cases?
struct ControlBlock
{
void* ptr;
};
template<typename T>
class IndirectPointer
{
public:
// Constructor used immediately after object allocation
IndirectPointer(ControlBlock* control_block) :
control_block(control_block),
offset(0)
{
}
// Allow indirect pointers to base clases
template<typename Derived>
IndirectPointer(const IndirectPointer<Derived>& r) requires std::is_base_of_v<T, Derived> :
control_block(r.control_block)
{
if (const Derived* ptr = r.try_get())
{
offset = r.offset + reinterpret_cast<const char*>(static_cast<const T*>(ptr)) - reinterpret_cast<const char*>(ptr);
}
}
// Access value
T* try_get() const
{
if (control_block && control_block->ptr)
{
return reinterpret_cast<T*>(static_cast<char*>(control_block->ptr) + offset);
}
return nullptr;
}
private:
template<typename T>
friend class IndirectPointer;
ControlBlock* control_block;
std::ptrdiff_t offset;
};
int main()
{
struct X {
~X() = default;
virtual void foo() = 0;
};
struct Y : X {
void foo() override {}
};
// Minimal code to show intented behaviour:
// Create object
Y object;
ControlBlock cb { &object };
IndirectPointer<Y> pointer{ &cb };
IndirectPointer<X> base_pointer{pointer};
// Move object
Y object_new_location{ std::move(object) };
// Update control block pointer
cb.ptr = &object_new_location;
// Pointer now points to new object
assert(pointer.try_get() == &object_new_location);
assert(base_pointer.try_get() == &object_new_location);
}
Clone()function) which yourIndirectPointer()can use to create a clone of the actual object as needed?