stdx.allocator

High-level interface for allocators. Implements bundled allocation/creation and destruction/deallocation of data including structs and classes, and also array primitives related to allocation. This module is the entry point for both making use of allocators and for their documentation.

Discussion

Synopsis:

  1. // Allocate an int, initialize it with 42 int* p = theAllocator.make!int(42); assert(*p == 42); // Destroy and deallocate it theAllocator.dispose(p); // Allocate using the global process allocator p = processAllocator.make!int(100); assert(*p == 100); // Destroy and deallocate processAllocator.dispose(p); // Create an array of 50 doubles initialized to -1.0 double[] arr = theAllocator.makeArray!double(50, -1.0); // Append two zeros to it theAllocator.expandArray(arr, 2, 0.0); // On second thought, take that back theAllocator.shrinkArray(arr, 2); // Destroy and deallocate theAllocator.dispose(arr);




D's allocators have a layered structure in both implementation and documentation:

  1. A high-level, dynamically-typed layer (described further down in this module). It consists of an interface called , which concret; allocators need to implement. The interface primitives themselves are oblivious to the type of the objects being allocated; they only deal in void[], by necessity of the interface being dynamic (as opposed to type-parameterized). Each thread has a current allocator it uses by default, which is a thread-local variable of type . The process has a global allocator called , also of type . When a new thread is created, is copied into . An application can change the objects to which these references point. By default, at application startup, refers to an object that uses D's garbage collected heap. This layer also include high-level functions such as and that comfortably allocate/create and respectively destroy/deallocate objects. This layer is all needed for most casual uses of allocation primitives.


  2. A mid-level, statically-typed layer for assembling several allocators into one. It uses properties of the type of the objects being created to route allocation requests to possibly specialized allocators. This layer is relatively thin and implemented and documented in the module. It allows an interested user to e.g. use different allocators for arrays versus fixed-sized objects, to the end of better overall performance.


  3. A low-level collection of highly generic heap building blocks Lego-like pieces that can be used to assemble application-specific allocators. The real allocation smarts are occurring at this level. This layer is of interest to advanced applications that want to configure their own allocators. A good illustration of typical uses of these building blocks is module which defines a collection of frequently- used preassembled allocator objects. The implementation and documentation entry point is . By design, the primitives of the static interface have the same signatures as the primitives but are for the most part optional and driven by static introspection. The parameterized class offers an immediate and useful means to package a static low-level allocator into an implementation of .


  4. Core allocator objects that interface with D's garbage collected heap (), the C malloc family (), and the OS (). Most custom allocators would ultimately obtain memory from one of these core allocators.




As of this time, is not integrated with D's built-in operators that allocate memory, such as new, array literals, or array concatenation operators. That means is opt-inapplications need to make explicit use of it.

For casual creation and disposal of dynamically-allocated objects, use , , and the array-specific functions , , and . These use by default D's garbage collected heap, but open the application to better configuration options. These primitives work either with theAllocator but also with any allocator obtained by combining heap building blocks. For example:

  1. void fun(size_t n) { // Use the current allocator int[] a1 = theAllocator.makeArray!int(n); scope(exit) theAllocator.dispose(a1); ... }


To experiment with alternative allocators, set for the current thread. For example, consider an application that allocates many 8-byte objects. These are not well supported by the default allocator, so a would be recommended. To install one in main, the application would use:

  1. void main() { import stdx.allocator.building_blocks.free_list : FreeList; theAllocator = allocatorObject(FreeList!8()); ... }




As with any global resource, setting theAllocator and processAllocator should not be done often and casually. In particular, allocating memory with one allocator and deallocating with another causes undefined behavior. Typically, these variables are set during application initialization phase and last through the application.

To avoid this, long-lived objects that need to perform allocations, reallocations, and deallocations relatively often may want to store a reference to the allocator object they use throughout their lifetime. Then, instead of using theAllocator for internal allocation-related tasks, they'd use the internally held reference. For example, consider a user-defined hash table:

  1. struct HashTable { private IAllocator _allocator; this(size_t buckets, IAllocator allocator = theAllocator) { this._allocator = allocator; ... } // Getter and setter IAllocator allocator() { return _allocator; } void allocator(IAllocator a) { assert(empty); _allocator = a; } }


Following initialization, the HashTable object would consistently use its object for acquiring memory. Furthermore, setting to point to a different allocator should be legal but only if the object is empty; otherwise, the object wouldn't be able to deallocate its existing state.



Allocators assembled from the heap building blocks don't need to go through IAllocator to be usable. They have the same primitives as IAllocator and they work with , , etc. So it suffice to create allocator objects wherever fit and use them appropriately:

  1. void fun(size_t n) { // Use a stack-installed allocator for up to 64KB StackFront!65536 myAllocator; int[] a2 = myAllocator.makeArray!int(n); scope(exit) myAllocator.dispose(a2); ... }


In this case, myAllocator does not obey the IAllocator interface, but implements its primitives so it can work with makeArray by means of duck typing.

One important thing to note about this setup is that statically-typed assembled allocators are almost always faster than allocators that go through IAllocator. An important rule of thumb is: "assemble allocator first, adapt to IAllocator after". A good allocator implements intricate logic by means of template assembly, and gets wrapped with IAllocator (usually by means of ) only once, at client level.

License

.

Authors

Source:

  • Declaration

    interface IAllocator;

    Dynamic allocator interface. Code that defines allocators ultimately implements this interface. This should be used wherever a uniform type is required for encapsulating various allocator implementations.

    Discussion

    Composition of allocators is not recommended at this level due to inflexibility of dynamic interfaces and inefficiencies caused by cascaded multiple calls. Instead, compose allocators using the static interface defined in , then adapt the composed allocator to IAllocator (possibly by using below).

    Methods returning return upon success, upon failure, and if the primitive is not implemented by the allocator instance.

    • Declaration

      abstract @property uint alignment();

      Returns the alignment offered.

    • Declaration

      abstract size_t goodAllocSize(size_t s);

      Returns the good allocation size that guarantees zero internal fragmentation.

    • Declaration

      abstract void[] allocate(size_t, TypeInfo ti = null);

      Allocates n bytes of memory.

    • Declaration

      abstract void[] alignedAllocate(size_t n, uint a);

      Allocates n bytes of memory with specified alignment a. Implementations that do not support this primitive should always return null.

    • Declaration

      abstract void[] allocateAll();

      Allocates and returns all memory available to this allocator. Implementations that do not support this primitive should always return null.

    • Declaration

      abstract bool expand(ref void[], size_t);

      Expands a memory block in place and returns true if successful. Implementations that don't support this primitive should always return false.

    • Declaration

      abstract bool reallocate(ref void[], size_t);

      Reallocates a memory block.

    • Declaration

      abstract bool alignedReallocate(ref void[] b, size_t size, uint alignment);

      Reallocates a memory block with specified alignment.

    • Declaration

      abstract Ternary owns(void[] b);

      Returns if the allocator owns , if the allocator doesn't own , and if ownership cannot be determined. Implementations that don't support this primitive should always return Ternary.unknown.

    • Declaration

      abstract Ternary resolveInternalPointer(const void* p, ref void[] result);

      Resolves an internal pointer to the full block allocated. Implementations that don't support this primitive should always return Ternary.unknown.

    • Declaration

      abstract bool deallocate(void[] b);

      Deallocates a memory block. Implementations that don't support this primitive should always return false. A simple way to check that an allocator supports deallocation is to call .

    • Declaration

      abstract bool deallocateAll();

      Deallocates all memory. Implementations that don't support this primitive should always return false.

    • Declaration

      abstract Ternary empty();

      Returns if no memory is currently allocated from this allocator, if some allocations are currently active, or if not supported.

  • Declaration

    interface ISharedAllocator;

    Dynamic shared allocator interface. Code that defines allocators shareable across threads ultimately implements this interface. This should be used wherever a uniform type is required for encapsulating various allocator implementations.

    Discussion

    Composition of allocators is not recommended at this level due to inflexibility of dynamic interfaces and inefficiencies caused by cascaded multiple calls. Instead, compose allocators using the static interface defined in , then adapt the composed allocator to ISharedAllocator (possibly by using below).

    Methods returning return upon success, upon failure, and if the primitive is not implemented by the allocator instance.

    • Declaration

      abstract shared @property uint alignment();

      Returns the alignment offered.

    • Declaration

      abstract shared size_t goodAllocSize(size_t s);

      Returns the good allocation size that guarantees zero internal fragmentation.

    • Declaration

      abstract shared void[] allocate(size_t, TypeInfo ti = null);

      Allocates n bytes of memory.

    • Declaration

      abstract shared void[] alignedAllocate(size_t n, uint a);

      Allocates n bytes of memory with specified alignment a. Implementations that do not support this primitive should always return null.

    • Declaration

      abstract shared void[] allocateAll();

      Allocates and returns all memory available to this allocator. Implementations that do not support this primitive should always return null.

    • Declaration

      abstract shared bool expand(ref void[], size_t);

      Expands a memory block in place and returns true if successful. Implementations that don't support this primitive should always return false.

    • Declaration

      abstract shared bool reallocate(ref void[], size_t);

      Reallocates a memory block.

    • Declaration

      abstract shared bool alignedReallocate(ref void[] b, size_t size, uint alignment);

      Reallocates a memory block with specified alignment.

    • Declaration

      abstract shared Ternary owns(void[] b);

      Returns if the allocator owns , if the allocator doesn't own , and if ownership cannot be determined. Implementations that don't support this primitive should always return Ternary.unknown.

    • Declaration

      abstract shared Ternary resolveInternalPointer(const void* p, ref void[] result);

      Resolves an internal pointer to the full block allocated. Implementations that don't support this primitive should always return Ternary.unknown.

    • Declaration

      abstract shared bool deallocate(void[] b);

      Deallocates a memory block. Implementations that don't support this primitive should always return false. A simple way to check that an allocator supports deallocation is to call .

    • Declaration

      abstract shared bool deallocateAll();

      Deallocates all memory. Implementations that don't support this primitive should always return false.

    • Declaration

      abstract shared Ternary empty();

      Returns if no memory is currently allocated from this allocator, if some allocations are currently active, or if not supported.

  • Declaration

    nothrow @nogc @property @safe IAllocator theAllocator();
    nothrow @nogc @property @safe void theAllocator(IAllocator a);

    Gets/sets the allocator for the current thread. This is the default allocator that should be used for allocating thread-local memory. For allocating memory to be shared across threads, use (below). By default, ultimately fetches memory from , which in turn uses the garbage collected heap.

    Examples

    1. // Install a new allocator that is faster for 128-byte allocations. import stdx.allocator.building_blocks.free_list : FreeList; import stdx.allocator.gc_allocator : GCAllocator; auto oldAllocator = theAllocator; scope(exit) theAllocator = oldAllocator; theAllocator = allocatorObject(FreeList!(GCAllocator, 128)()); // Use the now changed allocator to allocate an array const ubyte[] arr = theAllocator.makeArray!ubyte(128); assert(arr.ptr); //...

  • Declaration

    @property shared(ISharedAllocator) processAllocator();
    @property void processAllocator(shared ISharedAllocator a);

    Gets/sets the allocator for the current process. This allocator must be used for allocating memory shared across threads. Objects created using this allocator can be cast to .

  • Declaration

    auto make(T, Allocator, A...)(auto ref Allocator alloc, auto ref A args);

    Dynamically allocates (using ) and then creates in the memory allocated an object of type , using (if any) for its initialization. Initialization occurs in the memory allocated and is otherwise semantically the same as . (Note that using creates a pointer to an (empty) array of s, not an array. To use an allocator to allocate and initialize an array, use described below.)

    Parameters

    T

    Type of the object being created.

    Allocator alloc

    The allocator used for getting the needed memory. It may be an object implementing the static interface for allocators, or an reference.

    A args

    Optional arguments used for initializing the created object. If not present, the object is default constructed.

    Return Value

    If is a class type, returns a reference to the created object. Otherwise, returns a pointing to the created object. In all cases, returns if allocation failed.

    Throws

    If 's constructor throws, deallocates the allocated memory and propagates the exception.

    Examples

    1. // Dynamically allocate one integer const int* p1 = theAllocator.make!int; // It's implicitly initialized with its .init value assert(*p1 == 0); // Dynamically allocate one double, initialize to 42.5 const double* p2 = theAllocator.make!double(42.5); assert(*p2 == 42.5); // Dynamically allocate a struct static struct Point { int x, y, z; } // Use the generated constructor taking field values in order const Point* p = theAllocator.make!Point(1, 2); assert(p.x == 1 && p.y == 2 && p.z == 0); // Dynamically allocate a class object static class Customer { uint id = uint.max; this() {} this(uint id) { this.id = id; } // ... } Customer cust = theAllocator.make!Customer; assert(cust.id == uint.max); // default initialized cust = theAllocator.make!Customer(42); assert(cust.id == 42); // explicit passing of outer pointer static class Outer { int x = 3; class Inner { auto getX() { return x; } } } auto outer = theAllocator.make!Outer(); auto inner = theAllocator.make!(Outer.Inner)(outer); assert(outer.x == inner.getX);

  • Declaration

    T[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length);
    T[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length, auto ref T init);
    Unqual!(ElementEncodingType!R)[] makeArray(Allocator, R)(auto ref Allocator alloc, R range) if (isInputRange!R && !isInfinite!R);
    T[] makeArray(T, Allocator, R)(auto ref Allocator alloc, R range) if (isInputRange!R && !isInfinite!R);

    Create an array of with elements using . The array is either default-initialized, filled with copies of , or initialized with values fetched from range.

    Parameters

    T

    element type of the array being created

    Allocator alloc

    the allocator used for getting memory

    size_t length

    length of the newly created array

    T init

    element used for filling the array

    R range

    range used for initializing the array elements

    Return Value

    The newly-created array, or if either was or allocation failed.

    Throws

    The first two overloads throw only if alloc's primitives do. The overloads that involve copy initialization deallocate memory and propagate the exception if the copy operation throws.

    Examples

    1. import std.algorithm.comparison : equal; static void test(T)() { T[] a = theAllocator.makeArray!T(2); assert(a.equal([0, 0])); a = theAllocator.makeArray!T(3, 42); assert(a.equal([42, 42, 42])); import std.range : only; a = theAllocator.makeArray!T(only(42, 43, 44)); assert(a.equal([42, 43, 44])); } test!int(); test!(shared int)(); test!(const int)(); test!(immutable int)();

  • Declaration

    bool expandArray(T, Allocator)(auto ref Allocator alloc, ref T[] array, size_t delta);
    bool expandArray(T, Allocator)(auto ref Allocator alloc, ref T[] array, size_t delta, auto ref T init);
    bool expandArray(T, Allocator, R)(auto ref Allocator alloc, ref T[] array, R range) if (isInputRange!R);

    Grows by appending more elements. The needed memory is allocated using . The extra elements added are either default- initialized, filled with copies of , or initialized with values fetched from range.

    Parameters

    T

    element type of the array being created

    Allocator alloc

    the allocator used for getting memory

    T[] array

    a reference to the array being grown

    size_t delta

    number of elements to add (upon success the new length of is )

    T init

    element used for filling the array

    R range

    range used for initializing the array elements

    Return Value

    upon success, if memory could not be allocated. In the latter case is left unaffected.

    Throws

    The first two overloads throw only if alloc's primitives do. The overloads that involve copy initialization deallocate memory and propagate the exception if the copy operation throws.

    Examples

    1. auto arr = theAllocator.makeArray!int([1, 2, 3]); assert(theAllocator.expandArray(arr, 2)); assert(arr == [1, 2, 3, 0, 0]); import std.range : only; assert(theAllocator.expandArray(arr, only(4, 5))); assert(arr == [1, 2, 3, 0, 0, 4, 5]);

  • Declaration

    bool shrinkArray(T, Allocator)(auto ref Allocator alloc, ref T[] array, size_t delta);

    Shrinks an array by elements.

    Discussion

    If , does nothing and returns false. Otherwise, destroys the last elements in the array and then reallocates the array's buffer. If reallocation fails, fills the array with default-initialized data.

    Parameters

    T

    element type of the array being created

    Allocator alloc

    the allocator used for getting memory

    T[] array

    a reference to the array being shrunk

    size_t delta

    number of elements to remove (upon success the new length of is )

    Return Value

    true upon success, false if memory could not be reallocated. In the latter case, the slice is left with default-initialized elements.

    Throws

    The first two overloads throw only if alloc's primitives do. The overloads that involve copy initialization deallocate memory and propagate the exception if the copy operation throws.

    Examples

    1. int[] a = theAllocator.makeArray!int(100, 42); assert(a.length == 100); assert(theAllocator.shrinkArray(a, 98)); assert(a.length == 2); assert(a == [42, 42]);

  • Declaration

    void dispose(A, T)(auto ref A alloc, auto ref T* p);
    void dispose(A, T)(auto ref A alloc, auto ref T p) if (is(T == class) || is(T == interface));
    void dispose(A, T)(auto ref A alloc, auto ref T[] array);

    Destroys and then deallocates (using ) the object pointed to by a pointer, the class object referred to by a or reference, or an entire array. It is assumed the respective entities had been allocated with the same allocator.

  • Declaration

    auto makeMultidimensionalArray(T, Allocator, size_t N)(auto ref Allocator alloc, size_t[N] lengths...);

    Allocates a multidimensional array of elements of type T.

    Parameters

    N

    number of dimensions

    T

    element type of an element of the multidimensional arrat

    Allocator alloc

    the allocator used for getting memory

    size_t[N] lengths

    static array containing the size of each dimension

    Return Value

    An N-dimensional array with individual elements of type T.

    Examples

    1. import stdx.allocator.mallocator : Mallocator; auto mArray = Mallocator.instance.makeMultidimensionalArray!int(2, 3, 6); // deallocate when exiting scope scope(exit) { Mallocator.instance.disposeMultidimensionalArray(mArray); } assert(mArray.length == 2); foreach (lvl2Array; mArray) { assert(lvl2Array.length == 3); foreach (lvl3Array; lvl2Array) assert(lvl3Array.length == 6); }

  • Declaration

    void disposeMultidimensionalArray(T, Allocator)(auto ref Allocator alloc, auto ref T[] array);

    Destroys and then deallocates a multidimensional array, assuming it was created with makeMultidimensionalArray and the same allocator was used.

    Parameters

    T

    element type of an element of the multidimensional array

    Allocator alloc

    the allocator used for getting memory

    T[] array

    the multidimensional array that is to be deallocated

    Examples

    1. struct TestAllocator { import stdx.allocator.common : platformAlignment; import stdx.allocator.mallocator : Mallocator; alias allocator = Mallocator.instance; private static struct ByteRange { void* ptr; size_t length; } private ByteRange[] _allocations; enum uint alignment = platformAlignment; void[] allocate(size_t numBytes) { auto ret = allocator.allocate(numBytes); _allocations ~= ByteRange(ret.ptr, ret.length); return ret; } bool deallocate(void[] bytes) { import std.algorithm.mutation : remove; import std.algorithm.searching : canFind; bool pred(ByteRange other) { return other.ptr == bytes.ptr && other.length == bytes.length; } assert(_allocations.canFind!pred); _allocations = _allocations.remove!pred; return allocator.deallocate(bytes); } ~this() { assert(!_allocations.length); } } TestAllocator allocator; auto mArray = allocator.makeMultidimensionalArray!int(2, 3, 5, 6, 7, 2); allocator.disposeMultidimensionalArray(mArray);

  • Declaration

    CAllocatorImpl!A allocatorObject(A)(auto ref A a) if (!isPointer!A);
    CAllocatorImpl!(A, Yes.indirect) allocatorObject(A)(A* pa);

    Returns a dynamically-typed built around a given statically- typed allocator of type . Passing a pointer to the allocator creates a dynamic allocator around the allocator pointed to by the pointer, without attempting to copy or move it. Passing the allocator by value or reference behaves as follows.

    Discussion

    • If has no state, the resulting object is allocated in static shared storage.
    • If has state and is copyable, the result will store a copy of it within. The result itself is allocated in its own statically-typed allocator.
    • If has state and is not copyable, the result will move the passed-in argument into the result. The result itself is allocated in its own statically-typed allocator.

    Examples

    1. import stdx.allocator.mallocator : Mallocator; IAllocator a = allocatorObject(Mallocator.instance); auto b = a.allocate(100); assert(b.length == 100); assert(a.deallocate(b)); // The in-situ region must be used by pointer import stdx.allocator.building_blocks.region : InSituRegion; auto r = InSituRegion!1024(); a = allocatorObject(&r); b = a.allocate(200); assert(b.length == 200); // In-situ regions can deallocate the last allocation assert(a.deallocate(b));

  • Declaration

    shared(CSharedAllocatorImpl!A) sharedAllocatorObject(A)(auto ref A a) if (!isPointer!A);
    shared(CSharedAllocatorImpl!(A, Yes.indirect)) sharedAllocatorObject(A)(A* pa);

    Returns a dynamically-typed built around a given statically- typed allocator of type . Passing a pointer to the allocator creates a dynamic allocator around the allocator pointed to by the pointer, without attempting to copy or move it. Passing the allocator by value or reference behaves as follows.

    Discussion

    • If has no state, the resulting object is allocated in static shared storage.
    • If has state and is copyable, the result will store a copy of it within. The result itself is allocated in its own statically-typed allocator.
    • If has state and is not copyable, the result will move the passed-in argument into the result. The result itself is allocated in its own statically-typed allocator.

  • Declaration

    class CAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect): IAllocator;

    Implementation of IAllocator using Allocator. This adapts a statically-built allocator type to IAllocator that is directly usable by non-templated code.

    Discussion

    Usually CAllocatorImpl is used indirectly by calling .

    • Declaration

      ref Allocator impl();

      The implementation is available as a public member.

    • Declaration

      this(Allocator* pa);

      The implementation is available as a public member.

    • Declaration

      @property uint alignment();

      Returns impl.alignment.

    • Declaration

      size_t goodAllocSize(size_t s);

      Returns impl.goodAllocSize(s).

    • Declaration

      void[] allocate(size_t s, TypeInfo ti = null);

      Returns impl.allocate(s).

    • Declaration

      void[] alignedAllocate(size_t s, uint a);

      If impl.alignedAllocate exists, calls it and returns the result. Otherwise, always returns null.

    • Declaration

      Ternary owns(void[] b);

      If Allocator implements owns, forwards to it. Otherwise, returns Ternary.unknown.

    • Declaration

      bool expand(ref void[] b, size_t s);

      Returns if defined, false otherwise.

    • Declaration

      bool reallocate(ref void[] b, size_t s);

      Returns .

    • Declaration

      bool alignedReallocate(ref void[] b, size_t s, uint a);

      Forwards to impl.alignedReallocate if defined, false otherwise.

    • Declaration

      bool deallocate(void[] b);

      If impl.deallocate is not defined, returns false. Otherwise it forwards the call.

    • Declaration

      bool deallocateAll();

      Calls impl.deallocateAll() and returns the result if defined, otherwise returns false.

    • Declaration

      Ternary empty();

      Forwards to impl.empty() if defined, otherwise returns Ternary.unknown.

    • Declaration

      void[] allocateAll();

      Returns impl.allocateAll() if present, null otherwise.

  • Declaration

    class CSharedAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect): ISharedAllocator;

    Implementation of ISharedAllocator using Allocator. This adapts a statically-built, shareable across threads, allocator type to ISharedAllocator that is directly usable by non-templated code.

    Discussion

    Usually CSharedAllocatorImpl is used indirectly by calling .

    • Declaration

      shared ref Allocator impl();

      The implementation is available as a public member.

    • Declaration

      shared this(Allocator* pa);

      The implementation is available as a public member.

    • Declaration

      shared @property uint alignment();

      Returns impl.alignment.

    • Declaration

      shared size_t goodAllocSize(size_t s);

      Returns impl.goodAllocSize(s).

    • Declaration

      shared void[] allocate(size_t s, TypeInfo ti = null);

      Returns impl.allocate(s).

    • Declaration

      shared void[] alignedAllocate(size_t s, uint a);

      If impl.alignedAllocate exists, calls it and returns the result. Otherwise, always returns null.

    • Declaration

      shared Ternary owns(void[] b);

      If Allocator implements owns, forwards to it. Otherwise, returns Ternary.unknown.

    • Declaration

      shared bool expand(ref void[] b, size_t s);

      Returns if defined, false otherwise.

    • Declaration

      shared bool reallocate(ref void[] b, size_t s);

      Returns .

    • Declaration

      shared bool alignedReallocate(ref void[] b, size_t s, uint a);

      Forwards to impl.alignedReallocate if defined, false otherwise.

    • Declaration

      shared bool deallocate(void[] b);

      If impl.deallocate is not defined, returns false. Otherwise it forwards the call.

    • Declaration

      shared bool deallocateAll();

      Calls impl.deallocateAll() and returns the result if defined, otherwise returns false.

    • Declaration

      shared Ternary empty();

      Forwards to impl.empty() if defined, otherwise returns Ternary.unknown.

    • Declaration

      shared void[] allocateAll();

      Returns impl.allocateAll() if present, null otherwise.