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_ptradds its own layer of reference counting around a type that itself doesn’t implement reference-counting (so it is possible to usestd::shared_ptr<int>for example);RefPtrexpects the object type to implement its own reference counting mechanism (forGObject::Objectsubclasses, that isg_object_refandg_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
RefPtrbeing created for the function argument, initialized by copying the existingmy_ref_ptrinstance. This increments the reference count of the object by callingg_object_refon it. - The function gets called, and consumes the temporary
RefPtr. - Later, the original
RefPtris deallocated, and its destructor decrements the reference count of the object by callingg_object_unrefon 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:
RefPtr<Gtk::Expression>manages an instance ofGtk::Expression, usinggtk_expression_refandgtk_expression_unref;RefPtr<Gsk::RenderNode>manages an instance ofGsk::RenderNode, usinggsk_render_node_refandgsk_render_node_unref;RefPtr<GLib::Variant>manages an instance ofGLib::Variant, usingg_variant_refandg_variant_unref. This also demonstrates that a type doesn’t even necessarily have to be a “class” (aGObject::TypeInstance) to be usable withRefPtr.
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.