Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dealing with unique_ptr in containers

I have a vector of unique_ptrs which point to Models, Meshes, etc like this:

std::vector<std::unique_ptr<Model>> mLoadedModels;

I choose unique_ptr because it automatically frees the data upon vector destructor, and also because later on if I need to for example reload all the models (due to OpenGL context tear down/created) I can just internally in my resource manager reset() and make it point to a new Model instance and it wouldnt affect the rest of the system.

My question though is, how would you share the contents of the vector with other systems? You cant just pass the unique_ptr around, because that would change the ownership (due to its unique_ptr), and I want sole ownership in the rersource manager.

The solution I came up with is the following, to wrap the access in the following struct:

template<typename T>
struct Handle
{
    Handle(std::unique_ptr<T>& resource) : mResource(resource)
    {
    }

    T& operator*()                  { return mResource.get(); }
    const T& operator*() const      { return mResource.get(); }
    T* operator->()                 { return mResource.get(); }
    const T* operator->() const     { return mResource.get(); }


private:
    std::unique_ptr<T>& mResource;
};

typedef Handle<Model> ModelPtr;

ModelPtr GetModel(const std::string& modelName);

// example:
ModelPtr monkey = GetModel("Monkey");
monkey->dance();

// reload resources, and then monkey dereferences to the new Model instance 

It feels abit gimmicky though, surely theres a better, more straightforward solution to this?

like image 581
KaiserJohaan Avatar asked Dec 20 '22 02:12

KaiserJohaan


1 Answers

There is a simple solution to this.

Pass around vec[n].get() -- raw pointers. So long as you don't store them, and always get them back from the owner, and the owner doesn't destroy them while you are using them, you are safe.

If you aren't willing to follow that level of discipline, what you need is a std::shared_ptr in the vector, and pass around and store std::weak_ptrs. The weak_ptrs will auto-invalidate when the last shared_ptr goes away (and by policy, the only persistent shared_ptr is the one in the owning vector).

This has the added advantage that if you are in the middle of doing work on an element and the vector clears itself, you don't segfault. You access a weak_ptr by .lock(), which returns a shared_ptr, during whose lifetime the raw pointer is guaranteed to be good.

The downside is that this ups the cost, the upside is that it allows for weak shared ownership and lazy notification of invalidation.

like image 59
Yakk - Adam Nevraumont Avatar answered Dec 24 '22 00:12

Yakk - Adam Nevraumont