MemSpan¶
Synopsis¶
#include "swoc/MemSpan.h"
-
template<typename T>
class MemSpan¶
MemSpan is a view on writable memory. This distinguishes it from classes like TextView.
The purpose of the class is to provide a compact description of a contiguous writeable block of
memory, carrying both location and size information to (hopefully) prevent these from becoming
separated. A MemSpan is a view because it never owns memory. It should be treated as a smart pointer that
carries size information. As text views are intended to replace passing a character pointer and size
separately, a memory span is intended to replace passing a pointer and a size as separate arguments.
MemSpan has two styles. The “void” style treats the memory as purely a block of memory without
internal structure. The “typed” style treats the memory as an array of a specific type. These
can be inter-converted, in the equivalent of type casting. The rules about
conversions between span types is modeled on pointer conversions. Therefore a typed memory span
implicitly converts to a void span, but a void span requires an explicit cast or rebinding to
convert to a typed memory span.
As with pointers, a constant span can refer to writable memory and
a non-constant span can refer to writable memory. Whether the memory referenced by the
span is writable depends on whether the type of the span is a constant type. E.g. MemSpan<char> refers
to writable memory and MemSpan<char const> does not.
Usage¶
A primary use of MemSpan is to generalize the type of a block of memory. For instance a function parameter
could be std::vector<alpha> for some type alpha. Calling the function, however, requires
the creation of a vector. This can require an unneeded memory allocation.
The other primary use is to work with chunks of memory embedded in larger containers. For instance in the
previous example, not only is creating another vector needed, but it also makes working with
subspans very difficult. Either copying is required or it can be worked around with various
additional parameters, or by reverting to the
most primitve and passing in a raw pointer and a count. It is much easier to have a MemSpan parameter type
which handles all of these cases with minimal complexity. E.g. a function like
int f(MemSpan<char> source) { ... }
can be passed data from a vector, an array, a raw pointer and size, stack memory, etc., including types not defined in the scope of the function.
Void Span¶
A MemSpan<void> describes an undifferentiated contiguous block of memory. It can be
constructed from either a pointer and length MemSpan< void >::MemSpan(value_arg *,
size_t) or a half open range of two pointers MemSpan< void >::MemSpan(value_arg *, value_arg * ).
A default constructed instance, or one constructed from nullptr has no
memory and zero size.
The implementation differentiates between void and void const in order to be const correct.
A void span implicitly converts to a void const span but not the other way as is the case for
void* and void const* pointers. A void const span is the “universal” span - all other
span types implicitly convert to this type.
The memory described by the instance can be obtained with the data method MemSpan< void >::data and the size with the size method MemSpan< void >::size().
Typed Span¶
A typed MemSpan treats the described memory as an array of the type. E.g., MemSpan<int>
treats its memory as an array of int, with a subscript operator.
The construction is the same as with the void case but the pointers must be of the
span type. As with pointers and arrays, the count is the number of instances of T not the size in
bytes.
Rebinding¶
A new MemSpan of a different type can be created from an existing instance. This is is called
“rebinding” and uses the templated rebind method. This is roughly
equivalent to type casting in that the original instance is unchanged. A new instance is created
that refers to the same memory but with different type information. As an example,
MemArena::alloc() returns a MemSpan<void>. If this is to be used for string data,
then the call could look like
auto span = arena.alloc(n).rebind<char>();
This would make span of type MemSpan<char> covering the same memory returned from
alloc.
Rebinding requires the size of the memory block to be an integral multiple of the size of the target type. That is, a binding that creates a partial object at the end of the array is forbidden.
Alignment¶
Rebinding on void spans can be done along with alignment using the align method. This can be done with a templated method for a type, or with an explicit alignment
argument of type size_t. It does not fail due to partial objects. Instead enough space is
discarded from the start of memory such that the returned span has the alignment required for
T and memory space that would yield a partial object is discarded from the end. The result is
the largest subspan that is memory aligned and has space for an integral number of instances.
Conversions¶
Memory spans will implicitly convert in a manner equivalent to raw pointers.
Non-const spans implicitly convert to constant spans of the same type.
Non-const spans implicitly convert to
MemSpan<void>.Any span will implicitly convert to
MemSpan<void const>.
Construction¶
The most common style of construction is to provide a pointer and size / count. For void spans this is the
number of bytes, while typed spans take a count as with arrays. E.g. for some type Alpha
Alpha array[6];
the corresponding span would be
MemSpan<Alpha> span(array, 6);
On the other hand the constructors understand arrays so this works as well
MemSpan<Alpha> span(array);
There is constructor support for any contiguous memory container that supports the data
and size methods. A span can be contructed from std::string, std::string_view,
or std::vector by passing the container. Internally this
std::vector<Alpha> av;
MemSpan<Alpha> aspan{av};
is treated as
std::vector<Alpha> av; MemSpan<Alpha> aspan{av.data(), av.size};
There are template deduction guides for std::array, std::vector, std::string,
and std::string_view so that the previous could also be done as
std::vector<Alpha> av;
MemSpan aspan{av};