Memory » CnxSharedPtr module

CnxSharedPtr(T) is a struct template similar to C++'s std::shared_ptr or Rust's Box for a "smart" pointer type that maintains (when used correctly) shared ownership of, and manages an object through, a contained pointer. The shared object is disposed of ("freed") when all CnxSharedPtr(T)s sharing its ownership have been explicitly freed, when all owners have gone out of scope when declared with the shared_scoped(T) tagged declaration, when reset (cnx_shared_ptr_reset) has been called on all owners, or some combination thereof.

CnxSharedPtr(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, SHARED_DELETER when instantiating the CnxSharedPtr(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 CnxSharedPtr(T) instance.

CnxSharedPtr(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 SHARED_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. SHARED_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/SharedPtr.h>. No functions will be defined. This is optional (but signals intent explicitly) - If required template parameters are defined and SHARED_DECL is not, then this will be inferred as true by default.
  2. SHARED_IMPL - Defining this to true signals to the implementation to define the functions necessary for the template instantation when you include <Cnx/SharedPtr.h>. This is not required to be paired with SHARED_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. SHARED_T - The type to be stored and managed in the shared_ptr (e.g. u32 or CnxString). This is required.
  2. SHARED_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 (SHARED_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 `CnxSharedPtr(MyStruct)` declaration
#define SHARED_T MyStruct
#define SHARED_DECL TRUE
#define SHARED_UNDEF_PARAMS TRUE
#include <Cnx/SharedPtr.h>
#undef SHARED_UNDEF_PARAMS

// in MyStruct.c

#include "MyStruct.h"

// instantiate `CnxSharedPtr(MyStruct)` declaration
#define SHARED_T MyStruct
#define SHARED_IMPL TRUE
#define SHARED_UNDEF_PARAMS TRUE
#include <Cnx/SharedPtr.h>
#undef SHARED_UNDEF_PARAMS

void takes_ownership(CnxSharedPtr(MyStruct) to_take);

void example(void) {
    // create a sharedly owned `MyStruct`
    shared_scoped(MyStruct) my_ptr = cnx_make_shared(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 CnxSharedPtrMyStruct.h

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

#include "MyStruct.h"

// instantiate `CnxSharedPtr(MyStruct)` declaration
#define SHARED_T MyStruct
#define SHARED_DECL TRUE
#define SHARED_UNDEF_PARAMS TRUE
#include <Cnx/SharedPtr.h>
#undef SHARED_UNDEF_PARAMS

// in CnxSharedPtrMyStruct.c

#include "CnxSharedPtrMyStruct.h"

// instantiate `CnxSharedPtr(MyStruct)` declaration
#define SHARED_T MyStruct
#define SHARED_IMPL TRUE
#define SHARED_UNDEF_PARAMS TRUE
#include <Cnx/SharedPtr.h>
#undef SHARED_UNDEF_PARAMS

// in MyStruct.c

void takes_ownership(CnxSharedPtr(MyStruct) to_take);

void example(void) {
    // create a sharedly owned `MyStruct`
    shared_scoped(MyStruct) my_ptr = cnx_make_shared(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, CnxSharedPtr(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 CnxSharedPtr(i32), i32_ptr, the equivalent function call for cnx_shared_ptr_release would be cnx_shared_ptr_i32_release(&i32_ptr).

Defines

#define CnxSharedPtr(T)
macro alias for a CnxSharedPtr(T) maintaining ownership of an allocated T
#define cnx_shared_ptr_new(T)
Creates a new sharedly, owned zero-initialized T
#define cnx_shared_ptr_new_with_allocator(T, allocator)
Creates a new sharedly owned, zero-initialized T, allocated with the given allocator.
#define cnx_shared_ptr_new_with_capacity(T, capacity)
Creates a new sharedly owned, zero-initialized, dynamically allocated array.
#define cnx_shared_ptr_new_with_capacity_and_allocator(T, capacity, allocator)
Creates a new sharedly owned, zero-initialized, dynamically allocated array, allocated with the given allocator.
#define cnx_shared_ptr_from(T, ptr)
Creates a new CnxSharedPtr(T) managing the given pointer.
#define cnx_shared_ptr_from_with_allocator(T, ptr, allocator)
Creates a new CnxSharedPtr(T) managing the given pointer, associated with the given allocator.
#define cnx_shared_ptr_free(T, self)
Frees the given CnxSharedPtr(T), calling the associated deleter on the managed object.
#define cnx_shared_ptr_release(self)
Releases the pointer managed by self from ownership.
#define cnx_shared_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_shared_ptr_clone(self)
Clones the given CnxSharedPtr(T)
#define cnx_shared_ptr_swap(self, other_ptr)
Swaps the managed objects of self and the CnxSharedPtr(T) pointed to by other_ptr
#define cnx_shared_ptr_get_deleter(self)
Returns the CnxDeleter(T) function pointer associated with the CnxSharedPtr(T) instantiation self is an instance of.
#define cnx_shared_ptr_as_bool(self)
Returns whether the given CnxSharedPtr(T) currently manages an object.
#define cnx_shared_ptr_get(self)
Returns a pointer to the object managed by self
#define cnx_shared_ptr_get_const(self)
Returns a pointer-to-const to the object managed by self
#define cnx_shared_ptr_get_mut(self)
Returns a pointer-to-non-const to the object managed by self
#define cnx_shared_ptr_at(self, index)
Returns a reference to the element at index in the array managed by self
#define cnx_shared_ptr_at_const(self, index)
Returns a reference-to-const to the element at index in the array managed by self
#define cnx_shared_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_shared(T, ...)
Creates a CnxSharedPtr(T) managing a T initialized with the given list of (potentially designated) initializers.
#define cnx_make_shared_with_allocator(T, allocator, ...)
Creates a CnxSharedPtr(T) managing a T initialized with the given list of (potentially designated) initializers, allocated with the given allocator.
#define cnx_make_shared_array(T, capacity)
Creates a CnxSharedPtr(T) managing the array type T with the given initial capacity.
#define cnx_make_shared_array_with_allocator(T, capacity, allocator)
Creates a CnxSharedPtr(T) managing the array type T with the given initial capacity, allocated with the given allocator.
#define SharedPtr(T)
Declare a CnxSharedPtr(T) with this declaration attribute/type to ensure it (and its managed object) is automatically freed when the CnxSharedPtr(T) goes out of scope.

Define documentation

#define CnxSharedPtr(T)

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

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

CnxSharedPtr(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, SHARED_DELETER when instantiating the CnxSharedPtr(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 CnxSharedPtr(T) instance.

CnxSharedPtr(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 SHARED_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 `CnxSharedPtr(MyStruct)`
#define SHARED_T MyStruct
#define SHARED_DECL TRUE
#define SHARED_IMPL TRUE
#define SHARED_UNDEF_PARAMS TRUE
#include <Cnx/SharedPtr.h>
#undef SHARED_UNDEF_PARAMS

void takes_ownership(CnxSharedPtr(MyStruct) to_take);

void example(void) {
    // create a sharedly owned `MyStruct`
    SharedPtr(MyStruct) my_ptr = cnx_make_shared(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, CnxSharedPtr(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 CnxSharedPtr(i32), i32_ptr, the equivalent function call for cnx_shared_ptr_release would be cnx_shared_ptr_i32_release(&i32_ptr).

#define cnx_shared_ptr_new(T)

Creates a new sharedly, owned zero-initialized T

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

#define cnx_shared_ptr_new_with_allocator(T, allocator)

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

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

#define cnx_shared_ptr_new_with_capacity(T, capacity)

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

Parameters
T - The array type to manage in the created CnxSharedPtr(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 CnxSharedPtr(T) managing a zero-initialized, dynamically allocated array.

#define cnx_shared_ptr_new_with_capacity_and_allocator(T, capacity, allocator)

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

Parameters
T - The array type to manage in the created CnxSharedPtr(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 CnxSharedPtr(T) managing a zero-initialized, dynamically allocated array.

#define cnx_shared_ptr_from(T, ptr)

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

Parameters
T - The type to manage in the created CnxSharedPtr(T)
ptr - The ptr to manage with the CnxSharedPtr(T).
Returns a CnxSharedPtr(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 CnxSharedPtr(T) to maintain ownership. If you have control over when allocation occurs, prefer cnx_shared_ptr_new, cnx_make_shared, or derivatives.

#define cnx_shared_ptr_from_with_allocator(T, ptr, allocator)

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

Parameters
T - The type to manage in the created CnxSharedPtr(T)
ptr - The ptr to manage with the CnxSharedPtr(T).
allocator - The allocator to allocator and eventually free the managed T with
Returns a CnxSharedPtr(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 CnxSharedPtr(T) to maintain ownership. If you have control over when allocation occurs, prefer cnx_shared_ptr_new_with_allocator or cnx_make_shared_with_allocator

#define cnx_shared_ptr_free(T, self)

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

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

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

#define cnx_shared_ptr_release(self)

Releases the pointer managed by self from ownership.

Parameters
self - The CnxSharedPtr(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_shared_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 CnxSharedPtr(T) to have its managed object replaced
new_ptr - The pointer to the object to transfer ownership of to self

#define cnx_shared_ptr_clone(self)

Clones the given CnxSharedPtr(T)

Parameters
self - The CnxSharedPtr(T) to clone
Returns a clone of self

#define cnx_shared_ptr_swap(self, other_ptr)

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

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

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

#define cnx_shared_ptr_get_deleter(self)

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

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

#define cnx_shared_ptr_as_bool(self)

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

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

#define cnx_shared_ptr_get(self)

Returns a pointer to the object managed by self

Parameters
self - The CnxSharedPtr(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_shared_ptr_get_const or cnx_shared_ptr_get_mut, respectively.

#define cnx_shared_ptr_get_const(self)

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

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

#define cnx_shared_ptr_get_mut(self)

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

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

#define cnx_shared_ptr_at(self, index)

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

Parameters
self - The CnxSharedPtr(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_shared_ptr_at_const or cnx_shared_ptr_at_mut, respectively.

#define cnx_shared_ptr_at_const(self, index)

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

Parameters
self - The CnxSharedPtr(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_shared_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 CnxSharedPtr(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_shared(T, ...)

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

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

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

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

Parameters
T - The type the CnxSharedPtr(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 CnxSharedPtr(T) managing a newly allocated T

#define cnx_make_shared_array(T, capacity)

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

Parameters
T - The array type the CnxSharedPtr(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 CnxSharedPtr(T) managing a newly allocated dynamic array of capacity capacity

#define cnx_make_shared_array_with_allocator(T, capacity, allocator)

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

Parameters
T - The array type the CnxSharedPtr(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 CnxSharedPtr(T) managing a newly allocated dynamic array of capacity capacity

#define SharedPtr(T)

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

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