Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RefPtr

Among peel smart pointer types, RefPtr<T> is the one that you’ll encounter early on and most often.

A RefPtr<T> owns a single reference to an instance of a reference-counted type (most commonly a subclass of GObject::Object). A RefPtr, while it exists, will keep the object alive, and once all RefPtrs that refer to an object (and any other references to the same object) go out of scope, the object will be destroyed.

A RefPtr can be made from a nullptr, or a plain pointer to an object:

#include <peel/Gtk/Gtk.h>

using namespace peel;

Gtk::Button *my_button = /* ... */;
RefPtr<Gtk::Widget> widget = nullptr;
/* Keep an additional reference on my_button */
widget = my_button;

A RefPtr can be dereferenced with -> just like a plain pointer:

#include <peel/Gtk/Gtk.h>

using namespace peel;

RefPtr<Gtk::Button> button = /* ... */;
button->set_label ("My Button");

RefPtr compared to std::shared_ptr

RefPtr is in many ways similar to std::shared_ptr. There is however an important difference:

  • std::shared_ptr adds its own layer of reference counting around a type that itself doesn’t implement reference-counting (so it is possible to use std::shared_ptr<int> for example);
  • RefPtr expects the object type to implement its own reference counting mechanism (for GObject::Object subclasses, that is g_object_ref and g_object_unref), and wraps that mechanism into a C++ smart pointer type.

This also enables RefPtr to be constructed directly from a plain pointer, without needing something like std::enable_shared_from_this.

Using RefPtr

The RefPtr class has reasonable implementations of various members, including copy and move constructors and assignment operators, that manage the reference count of the referenced object as expected. This means that various operations involving plain pointers and RefPtrs (and other peel pointer types like FloatPtr and WeakPtr) “just work” and manage the reference count of the object automatically and correctly.

For example, assume there are two functions, one of which accepts an object by a plain pointer, and another by RefPtr. You can just call both of them whether you have a plain pointer or a RefPtr:

#include <peel/RefPtr.h>

using namespace peel;

void takes_plain_ptr (Object *object);
void takes_ref_ptr (RefPtr<Object> object);

Object *my_plain_ptr = /* ... */;
RefPtr<Object> my_ref_ptr = /* ... */;

/* All of these work: */
takes_plain_ptr (my_plain_ptr);
takes_plain_ptr (my_ref_ptr);
takes_ref_ptr (my_plain_ptr);
takes_ref_ptr (my_ref_ptr);
takes_ref_ptr (std::move (my_ref_ptr));

When a RefPtr is used on an API boundary, meaning it is passed as an argument to a function or returned from one, it signifies transfer of ownership, also known as trasnfer full in GObject world. For instance, Gio::ListModel::get_object returns an owned reference to an object, and has the following signature in peel:

RefPtr<GObject::Object>
Gio::ListModel::get_object (unsigned position) noexcept;

Sometimes, it makes sense to create temporary RefPtrs in order to make sure an object is kept alive over some manipulation. For example, here’s how you would move a button between two boxes:

#include <peel/Gtk/Gtk.h>

using namespace peel;

Gtk::Box *box1 = /* ... */;
Gtk::Box *box2 = /* ... */;
Gtk::Button *button = /* ... */;
/* button is a child of box1, to be moved into box2 */

RefPtr<Gtk::Button> button_ref = button;
box1->remove (button);
/* box1 has dropped its reference on button here! */
box2->append (button);
/* box2 has added a reference on button,
 * so our RefPtr can be safely dropped.
 */

A much more common pattern is keeping a RefPtr as a member variable in a class, in order to make sure that the referenced object stays alive for as long as an instance of this class does:

#include <peel/Gtk/Gtk.h>
#include <peel/class.h>

using namespace peel;

class MyWidget : public Gtk::Widget
{
  /* ... */

private:
  RefPtr<Gio::ListModel> model;
  RefPtr<Gtk::Adjustment> h_adjustment, v_adjustment;
};

Copy and move semantics

Note that copy and move semantics apply to RefPtr as usual in C++. Passing a RefPtr into a function by copy, like here:

void takes_ref_ptr (RefPtr<Object> object);

RefPtr<Object> my_ref_ptr = /* ... */;

takes_ref_ptr (my_ref_ptr);

will result in:

  • A fresh new instance of RefPtr being created for the function argument, initialized by copying the existing my_ref_ptr instance. This increments the reference count of the object by calling g_object_ref on it.
  • The function gets called, and consumes the temporary RefPtr.
  • Later, the original RefPtr is deallocated, and its destructor decrements the reference count of the object by calling g_object_unref on it.

This may be what you wanted if you plan to keep using the object (and the RefPtr) after the function returns. On the other hand, if you pass the RefPtr “by move” (typically using std::move), like this:

void takes_ref_ptr (RefPtr<Object> object);

RefPtr<Object> my_ref_ptr = /* ... */;

takes_ref_ptr (std::move (my_ref_ptr));

the temporary RefPtr for the argument will “steal” the reference from my_ref_ptr. As the result, my_ref_ptr will be set to nullptr after the call, and there will be no extra g_object_ref/g_object_unref calls at runtime.

Casting

Upcasting means converting a pointer to a derived type into a pointer to its base type.

Upcasting should for the most part “just work” with RefPtr. In particular, it should be possible to pass an instance of RefPtr<DerivedType> (or a plain DerivedType * pointer) in places where an instance of RefPtr<BaseType> is expected.

Downcasting means converting a pointer to a base type into a pointer to a derived type, when we know that the dynamic type of the object te pointer points to actually matches the derived type.

The usual way to perform downcasting in peel is with the GObject::TypeInstance::cast method, like this:

#include <peel/Gtk/Gtk.h>

using namespace peel;

Gtk::Widget *widget = /* ... */;
/* We know that it's actually a button */
Gtk::Button *button = widget->cast<Gtk::Button> ();

When used with a RefPtr, this will dereference the RefPtr and call the usual cast method, which will return a plain pointer. To downcast the RefPtr itself (moving it), use the RefPtr::cast method. This method is defined on RefPtr itself rather than on the type it references, so in order to call it, make sure to use a dot (.) and not an arrow (->):

#include <peel/Gtk/Gtk.h>

using namespace peel;

RefPtr<Gtk::Widget> widget = /* ... */;
/* We know that it's actually a button */
RefPtr<Gtk::Button> button = std::move (widget).cast<Gtk::Button> ();

Non object-derived types

Even though RefPtr is most commonly used with types derived from GObject::Object, it also works with other types that support GObject-style reference counting semantics. For example:

Under the hood, reference counting for various types is implemented with an extensible mechanism using the “traits” design pattern (specializations of a RefTraits<T> template). If you’re getting compile errors mentioning “incomplete type RefTraits<Something>”, it’s likely the case that you’re trying to use RefPtr with some type that isn’t reference-counted. For example, here’s what happens if you try to use RefPtr<Gdk::RGBA>:

peel/RefPtr.h: In instantiation of ‘peel::RefPtr<T>::~RefPtr() [with T = peel::Gdk::RGBA]’:
test.cpp:8:28:   required from here
    8 |   RefPtr<Gdk::RGBA> rgba = nullptr;
      |                            ^~~~~~~
peel/RefPtr.h:115:27: error: incomplete type ‘peel::RefTraits<peel::Gdk::RGBA, void>’ used in nested name specifier
  115 |       RefTraits<T>::unref (ptr);
      |       ~~~~~~~~~~~~~~~~~~~~^~~~~

FFI

When using plain C APIs that have no peel wrappers, or when wrapping your C++ API into a plain C interface, you may need to “bridge” between RefPtr and plain pointers, whether transfer full or transfer none.

For transfer none pointers, just extract or assign a plain pointer using normal means such as static_cast (or C-style cast) and operator =:

/**
 * frob_button:
 * @button: (transfer none): a button to frob
 */
void
frob_button (GtkButton *button);

/**
 * find_button:
 *
 * Returns: (nullable) (transfer none) a button, or NULL if not found
 */
GtkButton *
find_button (void);

RefPtr<Gtk::Button> button = /* ... */;
GtkButton *c_button = GTK_BUTTON ((Gtk::Button *) button);
frob_button (c_button);

c_button = find_button ();
button = reinterpret_cast<Gtk::Button *> (c_button);

For trasnfer full pointers, use the RefPtr::adopt_ref static method and the release_ref method. (Note that release_ref must be called on an rvalue, for example the result of std::move).

/**
 * consume_button:
 * @button: (transfer full): a button to consume
 */
void
consume_button (GtkButton *button);

/**
 * produce_button:
 *
 * Returns: (transfer full) a produced button
 */
GtkButton *
produce_button (void);

RefPtr<Gtk::Button> button = /* ... */;
Gtk::Button *owned_plain_button = std::move (button).release_ref ();
GtkButton *owned_c_button = GTK_BUTTON (owned_plain_button);
consume_button (owned_c_button);

owned_c_button = produce_button ();
owned_plain_button = reinterpret_cast<Gtk::Button *> (owned_c_button);
button = RefPtr<Gtk::Button>::adopt_ref (owned_plain_button);

You can even use adopt_ref and release_ref to intentionally leak a reference owned by a RefPtr, and to intentionally make up a reference when there is none (perhaps to recreate a reference you’ve leaked earlier).

Note that these APIs are less “safe” then typical RefPtr usage. You take responsibility for maintaining the reference count and not messing it up.