I'm wondering about how to (using C++11 and hopefully with backwards (boost or TR1) compatible smart pointer types) achieve:
One class instance (ModelController
) owns a resource (InputConsumer
), while another component (InputSender
, which in this case is a singleton) has access to it.
The model is InputSender
holds a list of references to InputConsumers
, of which there will be many.
ModelController
may have none, one, or many InputConsumers
, and there may be many ModelController
s. The InputSender
is NOT aware.
Here's what would be nice: A way for InputSender
to track the InputConsumers
assigned to it, in such a way that it can find out for itself whether the individual InputConsumers
are valid or not.
It seems to me that weak_ptr
is perfect for this purpose as their use requires checking this condition.
If InputSender
stops tracking any of its weak_ptr
refs, nothing bad happens, the corresponding InputConsumer
s will just experience radio silence.
If a ModelController
is deleted, or if a ModelController
deletes some of its InputConsumer
s, any InputSender
s that have registered with them will recognize at the next time they try to access them that they no longer exist, and can clean up, without the need to send a message or do anything.
So the question is, is this an appropriate situation for using shared_ptr
and weak_ptr
? I wonder if shared_ptr
is completely appropriate because the InputConsumer
s are conceptually owned by their ModelController
s, so they should be member variables. I dunno how much sense it makes for ModelController
to only manage them via shared_ptr
. I can't tell if unique_ptr
works together with weak_ptr
. Am I supposed to just manage the shared_ptr
s in ModelController
's ctor/dtor?
There may also be a well-known (not to me!) design pattern that this falls into, so if anyone knows of such a thing please tell me.
I do not have a lot of expertise in shared pointers, but yes, this seems a very appropriate use of weak_ptr
.
In this case, you are just annoyed that:
InputConsumer
s directly as members of ModelController
s, since its a trivial ownership relation.shared_ptr
to make it work with a weak_ptr
.I think this is solved by using a shared_ptr
as an alias of a member object. According to C++.com:
Additionally, shared_ptr objects can share ownership over a pointer while at the same time pointing to another object. This ability is known as aliasing (see constructors), and is commonly used to point to member objects while owning the object they belong to.
I never did it myself, but this seems adapted to your situation:
InputConsumer
s members of ModelController
sshared_ptr
for each of themInputSender
using weak_ptr
sEDIT
Here is a complete minimal working example:
#include <iostream>
#include <memory>
using namespace std;
// A class to try our smart pointers on
struct Foo
{
Foo() { cout << "constructing Foo\n"; }
~Foo() { cout << "destructing Foo\n"; }
};
// A class that owns some Foo as members
struct Owner
{
// The actual members
Foo foo1;
Foo foo2;
// A fake shared pointer whose purpose is:
// 1) to be of type shared_ptr<>
// 2) to have the same lifetime as foo1 and foo2
shared_ptr<Owner> self;
// A fake deleter that actually deletes nothing
struct Deleter
{
void operator() (Owner *) { cout << "pretend to delete Owner\n"; }
};
Owner() : self(this, Deleter()) { cout << "constructing Owner\n"; }
~Owner() { cout << "destructing Owner\n"; }
};
// A class that holds a reference to a Foo
struct Observer
{
// A reference to a Foo, as a weak pointer
weak_ptr<Foo> foo_ptr;
Observer(const shared_ptr<Foo> & foo_ptr) : foo_ptr(foo_ptr)
{
cout << "constructing Observer\n";
}
~Observer() { cout << "destructing Observer\n"; }
void check()
{
if(foo_ptr.expired())
cout << "foo expired\n";
else
cout << "foo still exists\n";
}
};
int main()
{
// Create Owner, and hence foo1 and foo2
Owner * owner = new Owner;
// Create an observer, passing an alias of &(owner->foo1) to ctor
Observer observer(shared_ptr<Foo>(owner->self, &(owner->foo1)));
// Try to access owner->foo1 from observer
observer.check();
delete owner;
observer.check();
return 0;
}
It prints:
constructing Foo
constructing Foo
constructing Owner
constructing Observer
foo still exists
destructing Owner
pretend to delete Owner
destructing Foo
destructing Foo
foo expired
destructing Observer
The tricky part is to be able to create a weak_ptr
to owner->foo1
(would be the same for foo2
). To do that, we first need a shared_ptr
that is an alias of owner->foo1
. This can only be done by:
shared_ptr<Foo> alias(other_shared_ptr, &(owner->foo1));
where other_shared_ptr
is a shared_ptr<T>
whose lifetime is at least as long as the one of owner->foo1
. To achieve this, using a shared_ptr<T>
which is also a member of owner
is a good idea, since it ensures the lifetime would be the same. Finally, we need a valid non-null pointer to give to it, and since we don't want to create anything on the heap, we must use an existing object. this
is a good candidate, since we know it is valid and get destroyed only after its members are destroyed. Hence our other_shared_ptr
is for instance:
shared_ptr<Owner> self(this);
However, this means that when self
gets out of scope, i.e. during the destruction of owner
, it will call delete this
. We do not want this deletion to occur, otherwise this
would be deleted twice (which is undefined behaviour, in practice a segfault). Hence we also provide to the constructor of self
a Deleter that actually doesn't delete anything.
The rest of the code should be self-explanatory with the comments.