Yes, you need a cast:
--Code://this line is the problem
*mpCurrent = reinterpret_cast(T::uint8 *(*)(uin8*))&Dtor<T>;
Mats
Printable View
Yes, you need a cast:
--Code://this line is the problem
*mpCurrent = reinterpret_cast(T::uint8 *(*)(uin8*))&Dtor<T>;
Mats
Unless I misunderstand, isn't that the whole problem?
*mpCurrent is uint8, not uint8* (*)(uint8*).
I use uint8* because I'm considering the memory as a field of bytes. It's not a good approach, I'll grant you that.
I started out using void*, but you can't perform arithmetic on void*. So I couldn't increment the pointer when I was filling up the memory.
What I want to do is: put an object in the memory, increment the pointer of the size of the object, put a pointer to a static function in memory, increment the pointer of the size of the pointer to the function.
After a while of using this, my memory should look like this:
| Type1 obj1 | &Dtor<Type1> | Type2 obj2 | &Dtor<Type2> |
When I decide to clear my pool, I can just roll back on the Dtor functions, calling destructors for all the objects.
My problem here is that I can't just consider a pointer as an arithmetic value (which it has to be, in some way).
Oh, yes, of course. We would also have to create a pointer to function pointer that aliases the mpCurrent address (It's possible to do with a cast, but it gets REALLY messy, so it's much easier to read the code if you do two lines of it, one making a pointer to function pointer variable and sets it to mpcurrent (with a reinterpret_cast), the other that assigns this variabel with the Dtor function - and you don't need a cast on this line).
--
Mats
When you decide to clear your pool, wouldn't you just be looking at large typeless char buffer? How do you plan to know where one object ends and another starts?Quote:
After a while of using this, my memory should look like this:
| Type1 obj1 | &Dtor<Type1> | Type2 obj2 | &Dtor<Type2> |
When I decide to clear my pool, I can just roll back on the Dtor functions, calling destructors for all the objects.
I mean, wouldn't the user code have to tell your pool the location and size of what to look for? If so, wouldn't it negate any usefulness this whole thing might have (automatic destruction of objects??) - as it would be a lot simpler to call normal new and delete?
That's the point of having the Dtor function return the freed memory pointer. Any time a call a Dtor, I can update my pointer to be right after the next Dtor. I can roll back object destructors one by one, using that trick, without knowing the type at deallocation time, since the Dtor<T> is already pointing to the right place.
But how would you know how much memory Type1, Type2 etc occupies and where the Dtors are? (You might need to store the size as well.)
The Dtor knows the size of Type1, Type2, because of the template.
When I want to clear the pool, I rewind the current memory pointer by sizeof(DtorType), the pointer now points to a pointer to a templated Dtor function, the templated Dtor knows sizeof(T), I call the Dtor on the current pointer and it returns the new pointer to the free memory.
Here is the code for the pool cleaning function:
mpCurrent is the pointer to the current location of free memory in the pool.Code:void MemoryPool::Clear(uint8* pLimit)
{
while (mpCurrent > pLimit)
{
mpCurrent -= sizeof(DtorType);
DtorType dtor = *reinterpret_cast<DtorType*>(mpCurrent);
mpCurrent = dtor(mpCurrent);
}
}
This works now. It's missing a couple of features, and could do with some fine tuning but the basics are there. It's been implemented in the NGL/NUI framework. It's LGPL so you can check out the code. It's in nui2 > base > nuiMemoryPool.cpp and include > nuiMemoryPool.h
OK.
One thing it needs is disabling copy constructor and assignment operator.
I understand that the point is to avoid memory leaks easily. However, the public Clear method introduces difficulties of its own: if I want to use it, I have to know at all time the order in which items were added to the memory pool or I'll end up accessing destructed objects.
Also, couldn't Clear be templated, so the ugly reinterpret_cast would be hidden from the user?
I also wonder, what is the benefit of introducing verbous allocation code through the new operator, if the same could be probably achieved through a less verbous templated member function:
Edit: Ok the last point is probably not valid...Code:nuiMemoryPool pool(1024);
//why not
Test* pTest = pool.Allocate(Test(100));
//instead of
Test* pTest1 = new (pool, Wrapper<Test>()) Test(1);
True.
Indeed. The Clear method requires to keep the pointer to the first allocated object. I made a helper class, nuiMemoryPoolGuard, to that effect. It just keeps the pointer at the moment it's created, and Clears up to that pointer when it's destroyed. That way you can "stack" allocation contexts using scope.
I don't get that... It is hidden, isn't it? The user just has to call Clear, without knowing the type of the objects allocated.
Wouldn't your example imply copying the contents of the memory into the pool ? Your Test(100) has already been allocated on the heap...
You *could* Allocate<T>() then call a placement new. But that would introduce possibilities for errors, since you could create something else than what you asked for at that position in memory. Using the new operator avoids this. Also, if you have to use a placement new, it's not so terrible to use another "special" new.
Also, I've added the new[] operator to the pool, now.
I meant a difference between these (ignore the warnings):Quote:
I don't get that... It is hidden, isn't it? The user just has to call Clear, without knowing the type of the objects allocated.Quote:
Also, couldn't Clear be templated, so the ugly reinterpret_cast would be hidden from the user?
Code:void foo(char* p)
{
//...
}
template <class T>
void bar(T* t)
{
char* p = reinterpret_cast<char*>(t);
//...
}
int main()
{
int* n;
foo(reinterpret_cast<char*>(n));
bar(n);
}
Because the type is created and passed to a template method, the method definitely has access to the type information without having to use any additional syntax or wrapper class. The copy constructor could/would then be invoked on the type "contained" in the newly allocated memory.Quote:
Wouldn't your example imply copying the contents of the memory into the pool?
*shrug*Quote:
Also, if you have to use a placement new, it's not so terrible to use another "special" new.
From what I've seen, your requirement of using this 'new' is; I can't call 'delete p' and expect it to do the right thing. (Or at least, it looks that way so far.)
Soma
The goal of this memory pool is to have cheap allocations by just incrementing a pointer. Having to create an object, then copy it, then destroy the original would pretty much contravene the original purpose.
That's true. Normally, you wouldn't need to delete an object on a pool before you want to clear it. But I'm still thinking about how to put the syntax around it.
Then you should stop wasting your time. A memory pool is used to impose semantics on allocation/deallocation related to the size or type of memory required. Most memory pool implementations exist only to allow rapid allocation/deallocation for otherwise inexpensive objects. As the cost of construction and destruction grows the cost of allocation/deallocation by comparison becomes negligible. If the construction/destruction of an object is as expensive as allocation/deallocation then the pool would indeed by a waste, but not because the pool would provide no direct benefit; the programmer would need to cache as many such objects as required/possible.Quote:
The goal of this memory pool is to have cheap allocations by just incrementing a pointer. Having to create an object, then copy it, then destroy the original would pretty much contravene the original purpose.
A memory pool is not a bulk unloader or some sort of garbage collector, so don't use it like that.
Soma
I'm with phantomotap.
I think you'll find that making use of the regular new/delete feature is, on average, pretty close to "incrementing a pointer". I doubt that your implementation will be sufficently faster to make a big impact. It is probably better in that case, to study what allocatins are done often, and find a way to imrpove those portions of code.
--
Mats