Weak containers

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.

Note

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::pairs 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::weakmultimaps).

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:

Mcguffin containers

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 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.