I'm currently working on a project where i need to access objects from various places, pretty much like std::shared_ptr
. At a certain point (e.g. when a library is unloaded) i need to destruct these objects. It is crucial to the project that the object is really destructed at this point, i can not just hand out weak_ptr
and rely that no one keeps a shared_ptr
. If i would just hand out weak_ptr
to other parts of the program, anyone could call lock()
and store the shared_ptr
, delaying or preventing destruction of the objects. This would crash my program.
What happens in the background is this: I have a small number of classes providing generic access to yet unknown types, e.g. a class Object
providing a method virtual getChilds()=0;
. Libraries might then inherit from those classes with a class template <O> class TemplateObject;
implementing the pure virtual functions. This way other parts of the program, including a GUI, can access in a limited way objects of a type, which was unknown at compile time. But if i destruct the Object
with its virtual destructor, i call code that lives in the library - which obviously fails if i have already unloaded the library. I therefore need to make sure that the Object
is destructed when the library is unloaded. The GUI e.g. is then able to notice that the object is already gone and can no longer be used.
My Idea was to create two smart pointers, namely master_ptr
and slave_ptr
. The master_ptr
behaves pretty much like a unique_ptr
or like "the one and only" shared_ptr
, destructing the managed object. The slave_ptr
acts much like a weak_ptr
, just allowing a more direct access and hiding the option to get back the shared_ptr
. The managed objects would be non-copyable and non-movable to make sure there are no copies left somewhere.
My implementation looks like this:
class ObjectInvalid{};
template <typename O>
class slave_ptr {
private:
std::weak_ptr<O> m_ptr;
public:
slave_ptr() {}
slave_ptr(std::shared_ptr<O> ptr) : m_ptr(ptr) {}
O& operator*() const {
if (m_ptr.expired())
throw ObjectInvalid();
return m_ptr.lock().operator*();
}
O* operator->() const {
if (m_ptr.expired())
throw ObjectInvalid();
return m_ptr.lock().operator->();
}
void reset() {
m_ptr.reset();
}
};
template <typename O>
class master_ptr {
private:
std::shared_ptr<O> m_ptr;
public:
master_ptr() = default;
master_ptr(O* ptr) : m_ptr(ptr) {}
//non-copyable
master_ptr(const master_ptr&) = delete;
master_ptr& operator=(const master_ptr&) = delete;
//movable
master_ptr(master_ptr&& other) = default;
master_ptr& operator=(master_ptr&& other) = default;
O& operator*() const {
if (!m_ptr)
throw ObjectInvalid();
return m_ptr.operator*();
}
O* operator->() const {
if (!m_ptr)
throw ObjectInvalid();
return m_ptr.operator->();
}
//allows reset before going out of scope
void reset() {
m_ptr.reset();
}
//cast to slave
operator slave_ptr<O>() {
return slave_ptr<O>(m_ptr);
}
};
A small test tells me it works as i would expect, but before implementing it in a bigger project i wanted to aks here if theres any flaws i didn't see right now or if you could hint me to some improvements.
About priorities: Easy use and safety (no possible memory leaks, objects destructed when needed, exceptions thrown afterwards) are top priorities here. Performance is a "nice to have".
std::weak_ptr
actually? Can you please elaborate more in your question? I think it's exactly designed for that kind of situation. \$\endgroup\$