C Object Deletion#

Warning

This page is for amongoc developers and the documented components and behavior are not part of any public API guarantees.

amongoc contains several types, templates, and macros that aide in the automatic deletion of C objects. All objects should follow the given rules in order to ensure uniform deletion behavior across the codebase.

<mlib/delete.h> (header file)#

This is a C-compatible header that is mostly useful for C++ code only.

Object Deletion Mechanism#

  1. Any public C type that acts as an owner of resources, whether it is a pointer type or an aggregate type that contains resources, should have a specialization of mlib::unique_deleter. Note that explicit specialization is not usually necessary, and it will be done automatically when certain macros are used. Read below.

  2. A type that is deletable using mlib::unique_deleter meets the mlib::unique_deletable concept.

  3. For a type \(T\) which has subtle deletion behavior that is implemented as a hand-written C API function \(F\), use mlib_assoc_deleter to associate the function \(F\) with the type \(T\).

  4. For a simple struct type that contains resource-owning members which are themselves mlib::unique_deletable, use mlib_declare_member_deleter and use mlib_declare_c_deletion_function.

  5. Important: Ensure that trying to delete on a zero-initialized value of a type is well-defined (a-la calling delete on a null pointer, which is a safe no-op).

Deletion APIs#

void mlib::delete_unique(auto &inst)#

Delete the object inst using the unique_deleter mechanism. This function is only callable if such a specialization of unique_deleter exists.

Header:

mlib/delete.h

Tip

This is implemented as a stateless invocable object.

template<typename T>
concept mlib::unique_deletable#

Matches any type that can be passed to delete_unique()

template<typename T>
struct mlib::unique_deleter#

This struct template is used to determine how to automatically delete C objects of type T, including pointers and aggregates.

Header:

mlib/delete.h

This should only be used with types that are themselves trivially destructible. To delete a C object generically, use mlib::delete_unique(). Any specialization of unique_deleter is treated as a stateless invocable object which must provide an operator()()

void operator()(T &inst) const noexcept#

Any specialization of unique_deleter must have a function call operation that performs the actual deletion operation.

template<auto... MemPointers>
struct mlib::delete_members#

This struct template creates a deletion invocable that deletes the members of a struct.

Template Parameters:

MemPointers – Zero or more pointers-to-member-objects.

Header:

mlib/delete.h

When this deletion object is invoked on an instance of a type, for each member in MemPointers, mlib::delete_unique() will be invoked on that instance’s member in the listed order.

Tip

Instead of using this directly, generate a specialization of it using the mlib_declare_member_deleter macro within a struct body.

mlib_declare_member_deleter(...)#

This variadic macro should appear within the body of a C struct, and each macro argument should be a pointer to a member of that struct.

Header:

mlib/delete.h

When compiled as C, this expands to an empty declaration.

When compiled as C++, this expands to a nested typedef deleter which is a specialization of mlib::delete_members. This will notify the mlib::unique_deleter mechanism that deletion of the object should recursively delete the identified struct members.

Important

Don’t use this with mlib_assoc_deleter

mlib_declare_c_deletion_function(FuncName, Type)#

Declares and defines a C linkage function named by FuncName that accepts a Type by-value.

Important

Don’t use this with mlib_assoc_deleter

The body of that function will call mlib::delete_unique() with the instance of the value.

When compiled as C, this expands to the forward declaration of the function. When compiled as C++, this expands to an inline definition of that function. The function must appear in at least one C++ translation unit in order to emit this function’s definition.

mlib_assoc_deleter(Type, DeletionFunc)#

Creates a compile-time association between the given type and a C API deletion function. The function must be invocable with a modifiable lvalue of type Type.

Header:

mlib/delete.h

Important

Don’t use this with mlib_declare_c_deletion_function

When compiled as C, this expands to an empty declaration.

When compiled as C++, this expands to an explicit specialization of mlib::unique_deleter for the type Type, which will invoke DeletionFunc for that type.

The expansion of this macro should always appear within any translation unit that contains the definition of the associated type.

Examples#

Creating a simple deletable aggregate#

If I have an aggregate type that I want to make deletable using the mlib::unique_deleter mechanism, that can be done as follows:

#include <mlib/delete.h>

typedef struct user_info {
  mlib_str username;
  mlib_str domain;
  int      uid;
  // Declare the deletion mechanism:
  mlib_declare_member_deleter(&user_info::username,
                              &user_info::domain);
} user_info;

// Declare a C API function that invokes mlib::delete_unique
mlib_declare_c_deletion_function(user_info_delete, user_info);

This is C-compatible header content that declares a zero-initializable struct user_info, along with a C API function user_info_delete which takes an instance of user_info by-value and deletes the members of that object.

Creating a Custom Deletable Objects#

This example creates a type which is not simple to destroy, but we can still register it with the mlib::unique_deleter mechanism:

#include <mlib/delete.h>
#include <mlib/alloc.h>

typedef struct buncha_numbers {
  int*           integers;
  mlib_allocator alloc;
  size_t         n_numbers;
} buncha_numbers;

mlib_extern_c inline void buncha_numbers_delete(buncha_numbers n) {
  mlib_deallocate(n.alloc, n.integers, sizeof(int) * n.n_numbers);
}

// Associate our deletion function with unique_deleter
mlib_assoc_deleter(buncha_numbers, buncha_numbers_delete);

This is a C-compatible interface that declares a type buncha_numbers and has a C function buncha_numbers_delete. Because of the API guarantees of mlib_deallocate(), it is safe to call with a zero-initialized allocator as long as the pointer argument is also null.

The expansion of mlib_assoc_deleter will associate the C function buncha_numbers_delete with a mlib::unique_deleter specialization for the type buncha_numbers

Automatic Unique Objects#

Warning

This page is for amongoc developers and the documented components and behavior are not part of any public API guarantees.

<mlib/unique.hpp> (header file)#

Declares the mlib::unique class template

template<typename T, typename Del = unique_deleter<T>>
class mlib::unique#

Automatically takes ownership of instances of T using the given deleter.

Use with CTAD is supported.

Example

extern "C" mlib_str makes_a_string();
extern "C" void     takes_a_string(mlib_str s);

void cxx_function() {
  // The string returned by `makes_a_string` will be automatically destroyed
  auto s1 = mlib::unique(makes_a_string());
  // Alternatively, with implicit conversion:
  mlib::unique s2 = makes_a_string();

  // We can pass the string along using release():
  takes_a_string(std::move(s2).release());
}

Note

This type does not separately track whether is is moved-from or empty. It is up to the deleter to respect zero-initialized objects as being empty.

unique()#

Creates a zero-initialized instance of T

unique(T &&inst)#

Takes ownership of inst. inst will be reset to a zero-initialized value.

Note

This is an implicit conversion constructor.

unique(unique &&other)#
unique &operator=(unique &&other)#

Move from another unique instance. Destroys the currently held object and calls release() on other

[[nodiscard]] T release() &&#

Relinquish ownership of the held object. The current value is returned, and the held value is zero-initialized.

This is r-value qualified to emphasize the ownership transfer.

T &reset()#
T &reset(T &&value)#

Destroy the held value and replace it with either a zero-initialized instance or the given value.

T *operator->()#
const T *operator->() const#
T &operator*()#
const T &operator*() const#
T &get()#
const T &get() const#

Obtain access to the wrapped object.