LibCXX implements several variations of STL containers that store weak pointers, with an additional aspect that the weak pointer gets removed from the container automatically when its referenced object goes out of scope and gets destroyed.
The container itself is an ordinary reference-counted object (which can certainly have its own weak pointers), and it contains only weak pointers to other objects. When the weakly-referenced object goes out of scope and gets destroyed, not only does the weak pointer, in the weak container, becomes undefined, it gets removed from the container, automatically.
x::weaklist
is a template alias for a reference to
a container with
std::list
-like properties.
x::weaklistptr
is the corresponding x::ptr
template alias.
The x::weaklist
accepts
weak pointers:
#include <x/weaklist.H> class widgetObj; typedef x:ref<widgetObj> widget; typedef x::weaklist<widgetObj> weakwidgetlist; weakwidgetlist currentWidgets(weakwidgetlist::create()); // ... widget p=widget::create(); currentWidgets->push_back(p);
The template parameter is a reference-counted
object.
x::weaklist
and
x::weaklistptr
refer to a
std::list
-like objects whose
push_front
() and
push_back
() methods take a regular,
strong reference, but place a
weak pointer into the weak list.
push_front
() and
push_back
() do not retain the strong reference
they receive as a parameter, they retain only a weak pointer.
There is no erase
() method in the container.
When the weakly-referenced object goes out of scope and gets destroyed,
the weak pointer also gets removed from the container.
for (weakwidgetlist::base::iterator b=currentWidgets->begin(), e=currentWidgets->end(); b != e; ++b) { widgetptr p=b->getptr(); if (!p.null()) { // ...
Weak list containers offer
begin()
,
end()
,
empty()
,
size()
,
push_back()
, and
push_front()
.
Iteration over a weak list container iterates over
weak pointers. Use
getptr
() to recover a strong
reference.
It's possible that getptr
() might return
a null x::ptr
, even though the container automatically removes
weak pointers upon destruction of the referenced object.
When the last reference to the weakly-referenced object goes out of
scope and it gets destroyed, the weak pointer to the object normally
gets removed from the container automatically.
However, this is not possible when there are iterators still in scope.
They are currently iterating over the weak container, and the
container can't be modified as long as they remain in scope; so
the weak pointers do not get immediately removed, and
getptr
() subsequently returns
a null x::ptr
.
The weak pointer to the destroyed object gets eventually removed from the weak container, after the last weak container iterator goes out of scope and gets destroyed. Until that happens, it's possible for weak container iterators to iterate over a weak null pointer.
For this reason, iterators over a weak list, or a weak map, should be short lived, and not stashed away someplace. Where possible, use a range iterator.
x::weakmap
,
x::weakmultimap
,
x::weakunordered_map
,
x::weakunordered_multimap
,
define analogous containers with map-like properties:
#include <x/weakmap.H> // Or multimap.H class widgetObj; typedef x:ptr<widgetObj> widgetptr; typedef x::weakmap<std::string, widgetObj> weakwidgetmap; weakwidgetmap currentWidgets(weakwidgetmap::create()); // ... widgetptr p=widgetptr::create(); currentWidgets->insert(std::make_pair("left", p)); // ... weakwidgetmap::base::iterator iter=currentWidgets->find("left"); if (iter != currentWidgets->end()) { widgetptr p=iter->second.getptr(); if (!p.null()) { // ...
Like a weak list container's, the weak maps do not implement
erase
(); when the weakly-referenced object goes out
of scope and gets destroyed, its weak pointers gets removed
automatically, if no iterators exist at the time; or after the last
iterator goes out of scope and gets destroyed.
It follows that iterators over the maps' contents should have a brief
existence, to avoid the containers growing with undefined weak
pointers.
Not surprisingly, weak map iterators iterate over
std::pair
s containing the key, and its
corresponding weak pointer.
Weak map containers implement the following
std::map
/std::multimap
methods:
begin()
,
end()
,
empty()
,
size()
,
find()
,
count()
,
lower_bound()
,
upper_bound()
, and
equal_range()
(meaningful only for
x::weakmultimap
s).
insert()
adds a weak pointer to the map.
An alternative version of insert()
exists, that takes the key
and the pointer separately, without the need for an intermediate
std::pair
:
currentWidgets->insert("left", p);
The weak map container also offers operator[]
,
as syntactical sugar, but since the container is a reference-counted
object that must be dereferenced by a *
, this may
not be very useful.
#include <x/weakmultimap.H> class widgetObj; typedef x:ptr<widgetObj> widgetptr; typedef x::weakmultimap<std::string, widgetObj> weakwidgetmap; weakwidgetmap currentWidgets(weakwidgetmap::create()); // ... x::ref<widgetObj> w=currentWidgets->find_or_create("left", [] { return ref<widgerObj>::create(); });
Recall that although weak pointers get automatically removed from
weak containers when the referenced object goes out of scope and
gets destroyed, there are edge conditions when iterating over the
weak container iterates over a weak pointer
whose
getptr
() can return a
nullptr
.
For that reason, sometimes a weak multimap is a better choice instead of a
weak map for a key/value lookup mapping.
With a weak map, it's possible that the weak pointer continues to exist
in the map, for the given key, making it impossible to store a
weakly-referenced value for the given key.
With a weak multimap, or a weak unordered multimap,
if a key's value is a null weak pointer, a new
value for the same key can still be inserted into this map.
find_or_create
() is a convenient atomic
method that does the following:
The first parameter to find_or_create
() is
a map key.
find_or_create
() checks
if the map contains any key with the given value, whose
weak pointer returns a non-nullptr
reference, if
so find_or_create
() returns it.
The second parameter to find_or_create
() is
a lambda. The lambda must return an x::ref
to the map's
weakly-referenced object.
If the map does not contain any value for the given key, or if all
of them have weak pointers
that return a nullptr
:
find_or_create
() calls the lambda, inserts
the key, and the value returned from the lambda, then returns the
same value.
find_or_create
() is less useful with a
x::weakmap
or a
x::weakunordered_map
,
which are based on a
std::map
/std::unordered_map
,
that allow only one value per
key. As metioned before, weak pointers to destroyed objects
cannot get removed from the weak container as long as
any iterators
to the weak container are still in scope.
find_or_create
() might find an existing
value for the key in the x::weakmap
,
but unable to remove it.
find_or_create
() returns a x::ptr
, and
returns a null one, in that situation; but only after
the lambda gets called and returns its x::ref
.
Immediately afterwards,
find_or_create
() fails to insert the
new x::ref
, which then goes out of scope and gets destroyed, and a
null x::ptr
gets returned.
x::weakmultimap
's
and x::weakunordered_multimap
's
find_or_create
()
has no problems inserting
a new value for the same key, of course, and won't have this
problem; so its find_or_create
()
always returns a x::ref
Once something gets added to a
x::weaklist
,
x::weakmap
,
x::weakmultimap
,
x::weakunordered_map
, or
x::weakunordered_multimap
,
it remains in the weak container until the referenced object goes out
of scope and gets destroyed.
Alternatively,
x::mcguffinlist
,
x::mcguffinmap
,
x::mcguffinmultimap
x::mcguffinunordered_map
, or
x::mcguffinunordered_multimap
are based on their weak counterparts, but take
a mcguffin-based approach that
allows removal of referenced objects that still exist,
from their container.
#include <x/mcguffinlist.H> #include <x/mcguffinmap.H> #include <x/mcguffinmultimap.H> class widgetObj; typedef x::ref<widgetObj> widget; typedef x::ptr<widgetObj> widgetptr; x::mcguffinlist<widget> l=x::mcguffinlist<widget>::create(); x::mcguffinmap<int, widget> m=x::mcguffinmap<int, widget>::create(); x::mcguffinmultimap<int, widget> mm=x::mcguffinmultimap<int, widget>::create(); x::ref<x::obj> mcguffin=l->push_back(widget::create()); x::ptr<x::obj> mcguffin=m->insert(0, widget::create()); std::pair<x::ptr<x::obj>, widgeptrt> ref=m->find_or_create(0, [] { return widget::create(); }); for (x::mcguffinlist<widget>::base::iterator b=l->begin(), e=l->end(); b != e; ++b) { widgetptr p=b->getptr(); x::ptr<x::obj> mcguffin=b->mcguffin(); b->erase(); } for (x::mcguffinmultimap<widget>::base::iterator b=mm->begin(), e=mm->end(); b != e; ++b) { widgetptr p=b->second.getptr(); x::ptr<x::obj> mcguffin=b->second.mcguffin(); b->second.erase(); }
These mcguffin containers work the same as their weak counterparts, and implement the same methods; but with the following differences.
The first parameter to the
x::mcguffinlist
,
and the second parameter to the
x::mcguffinmap
,
x::mcguffinmultimap
x::mcguffinunordered_map
or
x::mcguffinunordered_multimap
templates must be a x::ref
.
x::mcguffinlist
's
push_front
() and
push_back
() return an opaque
x::ref<x::obj>
mcguffin.
x::mcguffinmap
's
and
x::mcguffinunordered_map
's
insert
()
returns an opaque
x::ptr<x::obj>
mcguffin.
A null x::ptr
gets returned if the value cannot be inserted into
the map because the key already exists.
x::mcguffinmultimap
and
x::mcguffinunordered-multimap
insert
()
returns an opaque
x::ref<x::obj>
mcguffin.
x::mcguffinmap
's,
x::mcguffinmultimap
,
x::mcguffinunordered_map
's
and
x::mcguffinunordered_multimap
find_or_create
() returns
std::pair
with both the mcguffin and the
value.
x::mcguffinmap
's and
x::mcguffinunordered_map
's
std::pair
contains x::ptr
s,
x::mcguffinmultimap
's and
x::mcguffinunordered_multimap
std::pair
contains x::ref
s.
x::mcguffinmap
's and
x::mcguffinunordered_map
's
std::pair
contains null x::ptr
s if it couldn't
create a new value for the key.
In all other situations, either the existing map's value and its
mcguffin, or the new value returned by the lambda and its mcguffin,
get returned.
The mcguffin owns a strong reference on the inserted object.
The iterators' value's getptr
() returns the
supposedly inserted object, just like the weak containers;
however the underlying weak containers actually
weakly-reference the corresponding mcguffins, so when the inserted
object's mcguffin goes out of scope and gets destroyed, the inserted
object gets removed from the container, even if other strong references
to the inserted object are still in some scope, somewhere.
The iterators' values also implement two additional methods:
x::ptr<x::obj> mcguffin();
Returns the mcguffin for the inserted object. A null
x::ptr
gets returned if the object is no longer in the container.
void erase();
Removes the value from the container, even if its mcguffin is
still in scope. Afterwards, the same iterator's value's
getptr
() will always return a null
x::ptr
, and the container entry gets removed when the iterator
goes out of scope.