Friday, August 20, 2010

Using Glib::RefPtr with STL sorted containers

It turns there's a bug in glibmm that prevents one from using Glib::RefPtr<T> with any of the STL sorted containers, e.g. std::map or std::set.

The issue is a side effect of Glib::RefPtr<T>:
  • declaring operators == and !=
  • declaring operator bool()
  • not declaring operators <, <=, >, >=

The operators == and != compare pointers for equality and work as expected. The operator bool() is defined to allow the following usage of a RefPtr:

if (ref_ptr)
    puts("is non-null");
else
    puts("is null");

STL sorted containers need operator< on the type they contain. Assuming p1 and p2 are of type Glib::RefPtr<T>, the following compiles just fine:

p1 < p2

This in turn means that e.g. std::set<Glib::RefPtr<T> > my_map; compiles. Unfortunately, with current version of glibmm, this breaks badly at runtime.

Why? As there's no custom operator<, the compiler interprets p1 < p2 like this:

(bool)p1 < (bool)p2

This is true iff p1 is NULL and p2 is non-NULL. The operators <=, >, >= also work on the results of operator bool(). So, depending on the pointers wrapped in p1 and p2, it's possible that the following would be true:

p1 != p2 && !(p1 < p2) && !(p1 > p2)

(this is true iff p1 and p2 wrap two different non-NULL pointers)

There's a bug report for the issue.

A workaround for the time being is to use a custom comparator like this:

template<typename T>
class RefPtrLess
{
public:
    bool operator() (const Glib::RefPtr<T> &a,
        const Glib::RefPtr<T> &b)
    {
        const T* const a_ptr = a.operator->();
        const T* const b_ptr = b.operator->();

        return a_ptr < b_ptr;
    }
};

typedef std::set<Glib::RefPtr<MyType>, RefPtrLess<MyType> > TMyTypeSet;