Skip to content

Owners and Handles

Albin Johansson edited this page Apr 2, 2023 · 2 revisions

Introduction

An important concept in the library is the notion of owners and handles. Essentially, some types come in two flavors, that differ in their ownership semantics. For instance, when working with a window instance, you don't have to worry about freeing the underlying SDL_Window, since that is automatically done when the window instance goes out of scope. On the other hand, a window_handle will do nothing with the associated SDL_Window when it goes out of scope, since it does not assume ownership of the associated window.

You can always create handles from owners, but you can't create owners from handles.

Terminology

  • Owner: A class with owning semantics, i.e. it automatically manages the lifetime of the associated resources.
  • Handle: A class with non-owning semantics, i.e. it performs no resource management at all.

Syntax

In most cases, you'd want to use the owning versions of the library types. As a result, owners have nicer and shorter names, e.g. window is the owning version of basic_window. Handle types always feature a _handle suffix. The handle version of basic_window is subsequently called window_handle.

Rationale

The argument for RAII types with strong ownership semantics is rather straight forward: they allow for clean, effortless, and exception-safe handling of runtime resources. However, it might not be immediately obvious why the non-owning types, such as window_handle, are useful. So let's look at a few simple examples demonstrating their utility.

Consider a code base with a lot of rendering functions that accept SDL_Renderer* parameters. If this code base wants to transition to use Centurion, it might not be able to immediately refactor all such functions. So it might be tempting to write something like the following in the meantime.

void draw(SDL_Renderer* r)
{
  cen::renderer renderer {r};

  // Render stuff...
}

The problem with this approach is that the renderer will be deleted at the end of the draw function, which is not desirable in this scenario. Instead, a renderer_handle should be used. This would enable use of the nicer renderer API without committing ownership to the local renderer instance. So a more appropriate version of the example would look like the following.

void draw(SDL_Renderer* r)
{
  cen::renderer_handle renderer {r};

  // Render stuff...
}

Furthermore, handles allow Centurion to provide wrappers for SDL functions that return non-owning raw pointers. One such function is SDL_GetWindowFromID, which returns SDL_Window*. It would be impossible to wrap such functions without the ability to specify the desired ownership semantics of Centurion types. But since we do have handles, there is indeed a Centurion function for SDL_GetWindowFromID: get_window. There are many similar examples throughout the library.

Clone this wiki locally