• Advertisement

• Popular Now

• 11
• 9
• 10
• 9
• 10
• Advertisement
• Advertisement
• Advertisement

Stored-by-Value Pool : weak_ptr and polymorphism = impossible?

This topic is 678 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I have a data structure : pool of values.   (not pool of pointers)

When I called create(), it will return Handle.

Here is a draft.  Everything is good so far.

    template<class T> class Pool{
std::vector<T> v;                   //store by value
Handle<T> create(){  .... }
}
template<class T> class Handle{
Pool<T>* pool_;
int pool_index_;
T* operator->() {
return pool_->v.at(pool_index_);
}
void destroy(){
pool_-> ... destroy()  .... mark "pool_index_" as unused, etc ....
}
}

Now I want Handle<> to support polymorphism.

Many experts have kindly advised me to use weak_ptr, but I am left in blank, don't know how to do it.

The major part that I am stuck is :-

- Should create() return weak_ptr, not Handle?
.... or should Handle encapsulate weak_ptr?

- If it return weak_ptr for user's program, ...

how weak_ptr would know pool_index_?  It doesn't have such field.

- If the user cast weak_ptr/Handle to a parent class pointer as this example, there are many issues as followed.

class B{}
class C : public B {
}
....
{
Pool<C> cs;
Handle<C> cPtr=cs.create();
Handle<A> aPtr=cPtr;     // casting   // expected to be valid, but don't know ... how?
aPtr->destroy()   ;                   // aPtr will invoke Pool<A>::destroy  which is wrong!
//     Pool<C> is the correct one, not Pool<A>
aPtr.operator->() ;                   // face the same problem
}

I know, I am noob, yeah.

Edit:

I found a solution, so I think I should share here.

The solution (suggested by 1litre ) is to let every Pool<T> derived from Pool_interface.

Pool_interface have get/set, everything manipulate in term of void*

Instead of caching

 Pool<T>* pool_;

I must caching :-

 Pool_interface* pool_;

The get/set have to be encapsult again, e.g.

return static_cast<T*>(pool_.get(index));
Edited by hyyou

Share this post

Share on other sites
Advertisement

Weak pointers are like normal pointers, except you cannot hold an object only by weak pointers, it gets deleted.

The typical use case is a cache using pointers to values. To understand, consider normal pointers to values only here. Basically, upon request you try to find a value in your cache. If it exists, you return a pointer to it. If it doesn't exist, you create the value, update your cache administration, and return a pointer to the new value.
The user program can just let go of the pointer when it's done with the value.

This will work, except you cannot detect 'user program can just let go of the pointer'. In other words, you have values in your cache, but no idea how often they are used, or whether they are used at all!
You cannot delete any value, since the user program may still have a pointer to it!

This is where weak pointers come in. In your cache administration, you use a weak pointer to the value, and you give normal pointers to the user program.
As soon as the user program dropped all pointers to a value in your cache, that value is only referenced by the weak pointer in your cache administration. Since weak pointers cannot prevent an object from getting deleted, the value gets removed when the user program drops the last value to it.
Your administration doesn't know this happened, but the next time you try to get that cached value through the weak pointer, it will fail. Then you can take appropriate action, eg by creating the value again.

To the user program, the cache is handing out normal pointers. There is no reason why you couldn't do poly-morphism on that.

Using a std::vector<T> for storing values will however break under polymorphism, at least if your sub-classes have different variable members. Perhaps it could work if you only overload methods in derived classes, but not sure about that. I'd use a T*, or a weak version of that.

Edited by Alberth

Share this post

Share on other sites

I'd use a T*, or a weak version of that.

:( , that is what I am doing, but thank Alberth.

I just hope that there is a way to cast Handle<Derive> to Handle<Base> while the destroy() is still valid.

Edited by hyyou

Share this post

Share on other sites

You -can- create a pull of types and put in other types. However, it is not possible with Vector. And a template normal template would most likely break under such usage. You can do this with placement new. But make sure that your new type and the pre-instantiated type is the same.

For Handle to support polymorphism, you need it to refer to some sort of base class that all other items had implemented in the past.

So think of everything refering to GameObject, or Resource. As for getting a Pool Index, you'll need to manually add that data field in yourself. You can just build a second class on top of that.

Edited by Tangletail

Share this post

Share on other sites

I'd use a T*, or a weak version of that.

:( , that is what I am doing, but thank Alberth.

I just hope that there is a way to cast Handle<Derive> to Handle<Base> while the destroy() is still valid.

Sure you do that?  Your vector storage says

template<class T> class Pool{
std::vector<T> v;                   //store by value

I don't think you can store any derived class from T in there.

Share this post

Share on other sites

@Alberth ;  No, I don't want to store Derived in std::vector<T>.  I know it is impossible.

I just want the handle to be safely cast-able, i.e. bad thing not happen after casting when operator->()  or destroy().  XD

Edit: An expert tell me a solution, I have edited to show it in OP.

Edited by hyyou

Share this post

Share on other sites

• Advertisement