Memory » CnxUniquePtr module

CnxUniquePtr(T) is a struct template similar to C++'s std::unique_ptr or Rust's Box for a "smart" pointer type that maintains (when used correctly) unique ownership of, and manages an object through, a contained pointer. The contained object is disposed of ("freed") when the CnxUniquePtr(T) is explicitly freed, when it goes out of scope when declared with the unique_scoped(T) tagged declaration, or when reset (cnx_unique_ptr_reset) is called on the CnxUniquePtr(T).

CnxUniquePtr(T) is allocator aware and stores its associated allocator in itself.

The contained object is "freed" by a CnxDeleter, a function taking a T* and a CnxAllocator, whose task is to properly cleanup/free/etc. anything associated with the T. This deleter can be supplied as a template parameter, UNIQUE_DELETER when instantiating the CnxUniquePtr(T) template. This template parameter is optional, and if not supplied the deleter used will be the default deleter, which will simply deallocate the T with the allocator associated with the given CnxUniquePtr(T) instance.

CnxUniquePtr(T) can own dynamically allocated array types, and provided functionality will be slightly different, though comparable, when T is an array type vs a singular type. To instantiate the template for an array type, the type must be typedefed, e.g.: typedef int int_arr[];, and the typedef provided as the UNIQUE_T template parameter.

Instantiation Requirements:

  1. a typedef of your type to to provide an alphanumeric name for it (so it can be used in template and macro parameters)

We recommend two routes for providing instantations for user-defined types, either:

  1. Provide the instantiation together with your type's public interface and implementation OR
  2. Provide it as a separate "template instantiation" .h/.c file pair.

We recommend option one when your type is typically used in tandem with the template instantation or if it requires it for its public interface. We recommend option two for all other circumstances, so that users don't pay the price (compile time, and potentially code size) of template instantiations if they don't use them.

Instantiation Mode Parameters

These signal to the implementation to instantiation the declarations, definitions, or both, for the template.

  1. UNIQUE_DECL (Optional) - Defining this to true signals to the implementation to declare the required type declarations and definitions, and any required or public function declarations when you include <Cnx/UniquePtr.h>. No functions will be defined. This is optional (but signals intent explicitly) - If required template parameters are defined and UNIQUE_DECL is not, then this will be inferred as true by default.
  2. UNIQUE_IMPL - Defining this to true signals to the implementation to define the functions necessary for the template instantation when you include <Cnx/UniquePtr.h>. This is not required to be paired with UNIQUE_DECL (you can declare the template, in a header for example, prior to defining it in a source file), but it is eventually required to be used in exactly one translation unit in the resulting binary in order to provide definitions for the template's functions. Failing to complete this instantiation-mode exactly once in exactly one translation unit in your build will cause linking errors due to missing or multiply defined functions.

Template Parameters

These provide the type or value parameters that the template is parameterized on to the template implementation. These should be #defined to their appropriate types/values.

  1. UNIQUE_T - The type to be stored and managed in the unique_ptr (e.g. u32 or CnxString). This is required.
  2. UNIQUE_DELETER - The deleter function to free the memory associated with the managed type and perform any necessary associated cleanup. This function must take a particular signature: For a type Y that evaluates to either:
  1. T (UNIQUE_T) if T is NOT an array type
  2. The element type of the array type T if T IS an array type. e.g. for an array type T, and an instance t, Y would be typeof(t[0])

The signature must be void name(Y* object, CnxAllocator allocator); where object is a pointer to the managed object and allocator is the allocator associated with the object (the one that allocated it, and will free it).

Example using instantiation method one:

// in MyStruct.h

// include for `TRUE` (could just use `1`)
#include <Cnx/Def.h>
// include for `i32`
#include <Cnx/BasicTypes.h>

typedef struct {
    i32 one;
    i32 two;
    i32 three;
} MyStruct;

// instantiate `CnxUniquePtr(MyStruct)` declaration
#define UNIQUE_T MyStruct
#define UNIQUE_DECL TRUE
#define UNIQUE_UNDEF_PARAMS TRUE
#include <Cnx/UniquePtr.h>
#undef UNIQUE_UNDEF_PARAMS

// in MyStruct.c

#include "MyStruct.h"

// instantiate `CnxUniquePtr(MyStruct)` declaration
#define UNIQUE_T MyStruct
#define UNIQUE_IMPL TRUE
#define UNIQUE_UNDEF_PARAMS TRUE
#include <Cnx/UniquePtr.h>
#undef UNIQUE_UNDEF_PARAMS

void takes_ownership(CnxUniquePtr(MyStruct) to_take);

void example(void) {
    // create a uniquely owned `MyStruct`
    unique_scoped(MyStruct) my_ptr = cnx_make_unique(MyStruct, .one = 2, .two = 4, .three = 1);

    // transfer ownership of the `MyStruct` object managed by `my_ptr` to `takes_ownership`
    // `my_ptr` now contains `nullptr`
    takes_ownership(ptr_move(my_ptr));

    // aside: `ptr_move` from is nearly equivalent to `move`, but it ensures that the contained
    // pointer is set to the correct `nullptr` constant (`NULL` is not necessarily 0 on all
    // platforms, and `move` is implemented with zero-setting on the moved-from value).
    // on platforms where `NULL` is guaranteed to be `0`, you can safely use `move` instead of
    // `ptr_move`
}

Example using instantiation method two:

// in MyStruct.h

// include for `i32`
#include <Cnx/BasicTypes.h>

typedef struct {
    i32 one;
    i32 two;
    i32 three;
} MyStruct;


// in CnxUniquePtrMyStruct.h

// include for `TRUE` (could just use `1`)
#include <Cnx/Def.h>

#include "MyStruct.h"

// instantiate `CnxUniquePtr(MyStruct)` declaration
#define UNIQUE_T MyStruct
#define UNIQUE_DECL TRUE
#define UNIQUE_UNDEF_PARAMS TRUE
#include <Cnx/UniquePtr.h>
#undef UNIQUE_UNDEF_PARAMS

// in CnxUniquePtrMyStruct.c

#include "CnxUniquePtrMyStruct.h"

// instantiate `CnxUniquePtr(MyStruct)` declaration
#define UNIQUE_T MyStruct
#define UNIQUE_IMPL TRUE
#define UNIQUE_UNDEF_PARAMS TRUE
#include <Cnx/UniquePtr.h>
#undef UNIQUE_UNDEF_PARAMS

// in MyStruct.c

void takes_ownership(CnxUniquePtr(MyStruct) to_take);

void example(void) {
    // create a uniquely owned `MyStruct`
    unique_scoped(MyStruct) my_ptr = cnx_make_unique(MyStruct, .one = 2, .two = 4, .three = 1);

    // transfer ownership of the `MyStruct` object managed by `my_ptr` to `takes_ownership`
    // `my_ptr` now contains `nullptr`
    takes_ownership(ptr_move(my_ptr));

    // aside: `ptr_move` from is nearly equivalent to `move`, but it ensures that the contained
    // pointer is set to the correct `nullptr` constant (`NULL` is not necessarily 0 on all
    // platforms, and `move` is implemented with zero-setting on the moved-from value).
    // on platforms where `NULL` is guaranteed to be `0`, you can safely use `move` instead of
    // `ptr_move`
}

Like other Cnx templates, CnxUniquePtr(T) provides its type-agnostic usage through a vtable pointer contained in the struct, and provides macros which wrap usage of the vtable, making access simpler. If you prefer to not use this method of access, you can call the typed functions directly by in-fixing the contained type in the associated function name. IE: for a CnxUniquePtr(i32), i32_ptr, the equivalent function call for cnx_unique_ptr_release would be cnx_unique_ptr_i32_release(&i32_ptr).

Defines

#define CnxUniquePtr(T)
macro alias for a CnxUniquePtr(T) maintaining ownership of an allocated T
#define cnx_unique_ptr_new(T)
Creates a new uniquely, owned zero-initialized T
#define cnx_unique_ptr_new_with_allocator(T, allocator)
Creates a new uniquely owned, zero-initialized T, allocated with the given allocator.
#define cnx_unique_ptr_new_with_capacity(T, capacity)
Creates a new uniquely owned, zero-initialized, dynamically allocated array.
#define cnx_unique_ptr_new_with_capacity_and_allocator(T, capacity, allocator)
Creates a new uniquely owned, zero-initialized, dynamically allocated array, allocated with the given allocator.
#define cnx_unique_ptr_from(T, ptr)
Creates a new CnxUniquePtr(T) managing the given pointer.
#define cnx_unique_ptr_from_with_allocator(T, ptr, allocator)
Creates a new CnxUniquePtr(T) managing the given pointer, associated with the given allocator.
#define cnx_unique_ptr_free(T, self)
Frees the given CnxUniquePtr(T), calling the associated deleter on the managed object.
#define cnx_unique_ptr_release(self)
Releases the pointer managed by self from ownership.
#define cnx_unique_ptr_reset(self, new_ptr)
Frees the object currently managed by self, and replaces it with the one pointed to by new_ptr
#define cnx_unique_ptr_swap(self, other_ptr)
Swaps the managed objects of self and the CnxUniquePtr(T) pointed to by other_ptr
#define cnx_unique_ptr_get_deleter(self)
Returns the CnxDeleter(T) function pointer associated with the CnxUniquePtr(T) instantiation self is an instance of.
#define cnx_unique_ptr_as_bool(self)
Returns whether the given CnxUniquePtr(T) currently manages an object.
#define cnx_unique_ptr_get(self)
Returns a pointer to the object managed by self
#define cnx_unique_ptr_get_const(self)
Returns a pointer-to-const to the object managed by self
#define cnx_unique_ptr_get_mut(self)
Returns a pointer-to-non-const to the object managed by self
#define cnx_unique_ptr_at(self, index)
Returns a reference to the element at index in the array managed by self
#define cnx_unique_ptr_at_const(self, index)
Returns a reference-to-const to the element at index in the array managed by self
#define cnx_unique_ptr_at_mut(self, index)
Returns a reference-to-non-const to the element at index in the array managed by self
#define cnx_make_unique(T, ...)
Creates a CnxUniquePtr(T) managing a T initialized with the given list of (potentially designated) initializers.
#define cnx_make_unique_with_allocator(T, allocator, ...)
Creates a CnxUniquePtr(T) managing a T initialized with the given list of (potentially designated) initializers, allocated with the given allocator.
#define cnx_make_unique_array(T, capacity)
Creates a CnxUniquePtr(T) managing the array type T with the given initial capacity.
#define cnx_make_unique_array_with_allocator(T, capacity, allocator)
Creates a CnxUniquePtr(T) managing the array type T with the given initial capacity, allocated with the given allocator.
#define UniquePtr(T)
Declare a CnxUniquePtr(T) with this declaration attribute/type to ensure it (and its managed object) is automatically freed when the CnxUniquePtr(T) goes out of scope.

Define documentation

#define CnxUniquePtr(T)

macro alias for a CnxUniquePtr(T) maintaining ownership of an allocated T

CnxUniquePtr(T) is a template for a "smart" pointer type that maintains (when used correctly) unique ownership of and manages an object through a contained pointer. The contained object is disposed of ("freed") when the CnxUniquePtr(T) is explicitly freed, when it goes out of scope when declared with the UniquePtr(T) tagged declaration, or when reset (Cnx_unique_ptr_reset) is called on the CnxUniquePtr(T).

CnxUniquePtr(T) is allocator aware and stores its associated allocator in itself.

The contained object is "freed" by a CnxDeleter, a function taking a T* and a CnxAllocator, whose task is to properly cleanup/free/etc. anything associated with the T. This deleter can be supplied as a template parameter, UNIQUE_DELETER when instantiating the CnxUniquePtr(T) template. This template parameter is optional, and if not supplied the deleter used will be the default deleter, which will simply deallocate the T with the allocator associated with the given CnxUniquePtr(T) instance.

CnxUniquePtr(T) can own dynamically allocated array types, and provided functionality will be slightly different, though comparable, when T is an array type or a singular type. To instantiate the template for an array type, the type must be typedefed, e.g.: typedef int int_arr[];, and the typedef provided as the UNIQUE_T template parameter.

Example:

// include for `TRUE`
#include <Cnx/Def.h>
// include for `i32`
#include <Cnx/BasicTypes.h>

// just for example's sake
typedef struct {
    i32 one;
    i32 two;
    i32 three;
} MyStruct;

// instantiate `CnxUniquePtr(MyStruct)`
#define UNIQUE_T MyStruct
#define UNIQUE_DECL TRUE
#define UNIQUE_IMPL TRUE
#define UNIQUE_UNDEF_PARAMS TRUE
#include <Cnx/UniquePtr.h>
#undef UNIQUE_UNDEF_PARAMS

void takes_ownership(CnxUniquePtr(MyStruct) to_take);

void example(void) {
    // create a uniquely owned `MyStruct`
    UniquePtr(MyStruct) my_ptr = cnx_make_unique(MyStruct, .one = 2, .two = 4, .three = 1);

    // transfer ownership of the `MyStruct` object managed by `my_ptr` to `takes_ownership`
    // `my_ptr` now contains `nullptr`
    takes_ownership(ptr_move(my_ptr));
}

Like other Cnx templates, CnxUniquePtr(T) provides its type-agnostic usage through a vtable pointer contained in the struct, and provides macros which wrap usage of the vtable, making access simpler. If you prefer to not use this method of access, you can call the typed functions directly by in-fixing the contained type in the associated function name. IE: for a CnxUniquePtr(i32), i32_ptr, the equivalent function call for cnx_unique_ptr_release would be cnx_unique_ptr_i32_release(&i32_ptr).

#define cnx_unique_ptr_new(T)

Creates a new uniquely, owned zero-initialized T

Parameters
T - The type to manage in the created CnxUniquePtr(T)
Returns a CnxUniquePtr(T) managing a zero-initialized T

#define cnx_unique_ptr_new_with_allocator(T, allocator)

Creates a new uniquely owned, zero-initialized T, allocated with the given allocator.

Parameters
T - The type to manage in the created CnxUniquePtr(T)
allocator - The allocator to allocator and eventually free the managed T with
Returns a CnxUniquePtr(T) managing a zero-initialized T

#define cnx_unique_ptr_new_with_capacity(T, capacity)

Creates a new uniquely owned, zero-initialized, dynamically allocated array.

Parameters
T - The array type to manage in the created CnxUniquePtr(T). Must be a typedef to an array type (e.g.: typedef i32 i32_array[];).
capacity - The capacity of the array, in number of elements
Returns a CnxUniquePtr(T) managing a zero-initialized, dynamically allocated array.

#define cnx_unique_ptr_new_with_capacity_and_allocator(T, capacity, allocator)

Creates a new uniquely owned, zero-initialized, dynamically allocated array, allocated with the given allocator.

Parameters
T - The array type to manage in the created CnxUniquePtr(T). Must be a typedef to an array type (e.g.: typedef i32 i32_array[];).
capacity - The capacity of the array, in number of elements
allocator - The allocator to allocator and eventually free the managed array with
Returns a CnxUniquePtr(T) managing a zero-initialized, dynamically allocated array.

#define cnx_unique_ptr_from(T, ptr)

Creates a new CnxUniquePtr(T) managing the given pointer.

Parameters
T - The type to manage in the created CnxUniquePtr(T)
ptr - The ptr to manage with the CnxUniquePtr(T).
Returns a CnxUniquePtr(T) managing the given pointer

T may be either an array type or a single object.

Useful when working with a legacy API that returns raw allocated object(s), but you still want use a CnxUniquePtr(T) to maintain ownership. If you have control over when allocation occurs, prefer cnx_unique_ptr_new, cnx_make_unique, or derivatives.

#define cnx_unique_ptr_from_with_allocator(T, ptr, allocator)

Creates a new CnxUniquePtr(T) managing the given pointer, associated with the given allocator.

Parameters
T - The type to manage in the created CnxUniquePtr(T)
ptr - The ptr to manage with the CnxUniquePtr(T).
allocator - The allocator to allocator and eventually free the managed T with
Returns a CnxUniquePtr(T) managing the given pointer

T may be either an array type or a single object.

Useful when working with a legacy API that returns raw allocated object(s), but you still want use a CnxUniquePtr(T) to maintain ownership. If you have control over when allocation occurs, prefer cnx_unique_ptr_new_with_allocator or cnx_make_unique_with_allocator

#define cnx_unique_ptr_free(T, self)

Frees the given CnxUniquePtr(T), calling the associated deleter on the managed object.

Parameters
T - The type managed in the given CnxUniquePtr(T)
self - The CnxUniquePtr(T) to free

This should generally only need to be called explicitly in particularly rare circumstances. In typical usage, CnxUniquePtr(T)s should be declared with the UniquePtr(T) tag, so cleanup is performed automatically when the associated CnxUniquePtr(T) goes out of scope

#define cnx_unique_ptr_release(self)

Releases the pointer managed by self from ownership.

Parameters
self - The CnxUniquePtr(T) to release ownership of its managed object
Returns the pointer to the object previously managed by self

Useful when you need to transfer ownership of the managed object from yourself to a another API that uses a different method of ownership management

#define cnx_unique_ptr_reset(self, new_ptr)

Frees the object currently managed by self, and replaces it with the one pointed to by new_ptr

Parameters
self - The CnxUniquePtr(T) to have its managed object replaced
new_ptr - The pointer to the object to transfer ownership of to self

#define cnx_unique_ptr_swap(self, other_ptr)

Swaps the managed objects of self and the CnxUniquePtr(T) pointed to by other_ptr

Parameters
self - The CnxUniquePtr(T) to have its managed object swapped
other_ptr - Pointer to the CnxUniquePtr(T) to swap managed objects with

self and the CnxUniquePtr(T) pointed to by other_ptr must manage the same type

#define cnx_unique_ptr_get_deleter(self)

Returns the CnxDeleter(T) function pointer associated with the CnxUniquePtr(T) instantiation self is an instance of.

Parameters
self - The CnxUniquePtr(T) to get the associated deleter of
Returns the CnxDeleter(T) function pointer associated with self

#define cnx_unique_ptr_as_bool(self)

Returns whether the given CnxUniquePtr(T) currently manages an object.

Parameters
self - The CnxUniquePtr(T) to check if currently manages an object
Returns true if self currently manages an object, otherwise false

#define cnx_unique_ptr_get(self)

Returns a pointer to the object managed by self

Parameters
self - The CnxUniquePtr(T) to get the managed object of
Returns const correct pointer to the managed object

This is const correct:

  • If self is const, this will return a pointer-to-const of the managed object.
  • Otherwise, this will return a "normal" pointer to the managed object. If you want to explicitly get a pointer-to-const or pointer-to-not-const, use cnx_unique_ptr_get_const or cnx_unique_ptr_get_mut, respectively.

#define cnx_unique_ptr_get_const(self)

Returns a pointer-to-const to the object managed by self

Parameters
self - The CnxUniquePtr(T) to get the managed object of
Returns pointer-to-const to the managed object

#define cnx_unique_ptr_get_mut(self)

Returns a pointer-to-non-const to the object managed by self

Parameters
self - The CnxUniquePtr(T) to get the managed object of
Returns pointer-to-non-const to the managed object

#define cnx_unique_ptr_at(self, index)

Returns a reference to the element at index in the array managed by self

Parameters
self - The CnxUniquePtr(T) to get the element from
index - The index of the element in the array managed by self
Returns const correct reference to the array element

This is const correct:

  • If self is const, this will return a reference-to-const of the array element.
  • Otherwise, this will return a "normal" reference to the array element. If you want to explicitly get a reference-to-const or reference-to-not-const, use cnx_unique_ptr_at_const or cnx_unique_ptr_at_mut, respectively.

#define cnx_unique_ptr_at_const(self, index)

Returns a reference-to-const to the element at index in the array managed by self

Parameters
self - The CnxUniquePtr(T) to get the element from
index - The index of the element in the array managed by self
Returns reference-to-const to the array element

#define cnx_unique_ptr_at_mut(self, index)

Returns a reference-to-non-const to the element at index in the array managed by self

Parameters
self - The CnxUniquePtr(T) to get the element from
index - The index of the element in the array managed by self
Returns reference-to-non-const to the array element

#define cnx_make_unique(T, ...)

Creates a CnxUniquePtr(T) managing a T initialized with the given list of (potentially designated) initializers.

Parameters
T - The type the CnxUniquePtr(T) will manage
... - The list of initializers to initialize the T with
Returns a CnxUniquePtr(T) managing a newly allocated T

#define cnx_make_unique_with_allocator(T, allocator, ...)

Creates a CnxUniquePtr(T) managing a T initialized with the given list of (potentially designated) initializers, allocated with the given allocator.

Parameters
T - The type the CnxUniquePtr(T) will manage
allocator - The CnxAllocator to allocate and eventually free the T with
... - The list of initializers to initialize the T with
Returns a CnxUniquePtr(T) managing a newly allocated T

#define cnx_make_unique_array(T, capacity)

Creates a CnxUniquePtr(T) managing the array type T with the given initial capacity.

Parameters
T - The array type the CnxUniquePtr(T) will manage. Must be a typedef to an array type (e.g.: typedef i32 i32_array[];)
capacity - The capacity of the array in number of elements
Returns a CnxUniquePtr(T) managing a newly allocated dynamic array of capacity capacity

#define cnx_make_unique_array_with_allocator(T, capacity, allocator)

Creates a CnxUniquePtr(T) managing the array type T with the given initial capacity, allocated with the given allocator.

Parameters
T - The array type the CnxUniquePtr(T) will manage. Must be a typedef to an array type (e.g.: typedef i32 i32_array[];)
capacity - The capacity of the array in number of elements
allocator - The CnxAllocator to allocate and eventually free the array with
Returns a CnxUniquePtr(T) managing a newly allocated dynamic array of capacity capacity

#define UniquePtr(T)

Declare a CnxUniquePtr(T) with this declaration attribute/type to ensure it (and its managed object) is automatically freed when the CnxUniquePtr(T) goes out of scope.

Parameters
T - The type managed by the CnxUniquePtr(T)