Reputation: 1470
I have recently been reading about custom memory allocators for c++ and came across an intressting concept where rather than using pointers "handles" are used which are effectively pointers to pointers, this allows the allocator to rearrange its memory to avoid fragmentation while avoiding the problem of invalidating all the pointers to the allocated memory.
However, different allocators may wish to use handles differently, for example a pool allocator would have no need to rearrange its memory, where as other allocators would. Those that need to rearrange their memory may need to treat handles as pointers to pointers, indexes to an array of pointers, etc whereas allocators that do not rearrange their memory would treat handles as a simple pointer. Ideally each allocator would be able to use a different type of handle so that it could achieve optimum performance, having a base handle class with virtual methods would incur a lot of overhead as handles would be used every time you needed to access any function/member of a class allocated dynamically.
My solution was to use partial template specialization so that the handle type was worked out at compile time, removing the run time overhead of virtuals and allowing the compiler to do other optimizations (eg: inlining)
/////////////////////////////////////////////////
/// \brief The basic handle class, acts as simple pointer
/// Single layer of indirection
/////////////////////////////////////////////////
template <typename T>
class Handle{
public:
T* operator->(){return obj;}
//other methods...
private:
T* obj;
};
/////////////////////////////////////////////////
/// \brief Pointer specialization of the handle class, acts as a pointer to pointer
/// allowing allocators to rearrange their data
/////////////////////////////////////////////////
template <typename T>
class Handle<T *>{
public:
T* operator->(){return *obj;};
//other methods...
private:
T** obj;
};
This works perfectly and allows allocators to return whichever handle type they need, however it means that any function that needs to take a handle as a parameter needs to be overloaded to accept both types of specialization, also a class holding a handle as a member will need to be templated as to whether it has a normal handle, pointer to pointer handle or some other type.
This problem only gets worse as more handle types are added or a function takes more than one handle and all combinations of handle types must be given an overload.
Either I need to be able to make all handles that point to an instance of "TypeA" have the type Handle<TypeA>
and then use a different method to template specialization to provide the different functionality or somehow hide the template parameter from any code using the handles. How could this be achieved?
(This method of hiding template parameters would also be useful in other instances, for example in a policy based logging system where a class may wish to hold a reference to any type of logger without itself being templated. Obviously in the case of logging virtual inheritance could be used as the dominating factor in speed would be the I/O rather than function call overhead)
Upvotes: 0
Views: 469
Reputation: 46
I have implemented a memory system that allowed exactly what you describe, but could not think of a way to have unique handle types without virtual functions. The template parameters are part of the type.
In the end I made a single handle type and used the least significant bit of the pointer to store whether it was a direct or indirect pointer. Before dereferencing I would check the bit and if it was not set I would simply return the pointer, otherwise I would unset the bit and ask the memory system for the actual pointer.
The scheme did work but I eventually removed the indirect memory handle support from my memory system as I found that the overheads could be quite high and because it was so intrusive on all aspects of my code. Basically almost everywhere a pointer would normally be used I had to use a handle instead. It also required memory to be locked before use on other threads so that it wasn't defragmented while in use. Finally it required me to write entirely custom containers in order to get acceptable performance. I didn't want a double indirection on every access of a vector in a loop for example.
Upvotes: 3