The Guidelines Support Library (GSL) is a small library for supporting the rules of the C++ Core Guidelines. The GSL consists of components such as views, ownership pointers, assertions, utilities, and concepts.
The best-known implementation of the GSL is the one from Microsoft, hosted at GitHub: Microsoft/GSL (https://github.com/Microsoft/GSL). The Microsoft version requires C++14 support and runs on various platforms. But that is not all; more implementations are available on GitHub. I want to explicitly emphasize the GSL-lite implementation of Martin Moene. His implementation even works with C++98 and C++03.
This section does not present the GSL in detail but provides a first introduction. For your further investigation, use the concrete implementations such as Microsoft/GSL or GSL-lite.
The GSL consists of five components. I ignore the GSL concepts in this overview because they are already part of C++20. Appendix B, Concepts, gives you an introduction to concepts.
A view is never an owner. In the case of a gsl::span<T>
, it represents a nonowning range of continuous memory. This nonowning range can be an array, a pointer with a size, or a std::vector
. The same applies to gsl::string_span<T>
or zero-terminated C-strings: gsl::czstring
or gsl::wzstring
. The main reason for having a gsl::span<T>
is to prevent a situation where a plain array is decayed to a pointer if passed to a function; therefore, the size information would be lost.
gsl::span<T>
automatically deduces the size of the plain array or the std::vector
. If you use a pointer, you have to provide the size.
template <typename T> void copy_n(const T* p, T* q, int n){} template <typename T> void copy(gsl::span<const T> src, gsl::span<T> des){} int main(){ int arr1[] = {1, 2, 3}; int arr2[] = {3, 4, 5}; copy_n(arr1, arr2, 3); // (1) copy(arr1, arr2); // (2) }
In contrast to the function copy_n
(1), you do not have to provide the number of elements for the function copy
(2). Hence, a common cause of errors is gone with gsl::span<T>
. gsl::span<T>
is similar to std::span<T>
, which is part of C++20.
The GSL has various kinds of owners.
I assume that you know about std::unique_ptr
and std::shared_ptr
, and therefore, you know gsl::unique_ptr
and gsl::shared_ptr
. You may be wondering whether the GSL has its own smart pointers, because the C++11 standard has std::unique_ptr
and std::shared_ptr
. The answer is straightforward: You can use the GSL with a compiler that does not support C++11.
gsl::owner<T*>
is a pointer that has ownership of the referenced object. You should use gsl::owner<T>
if you cannot use resource handles such as smart pointers or containers. The crucial point is that you have to free the resource explicitly. Raw pointers that are not marked as gsl::owner<T*>
are considered nonowning in the C++ Core Guidelines (see “R.3: A raw pointer (a T*
) is non-owning” and “R.4: A raw reference (a T&
) is non-owning”). Consequently, you don’t have to free the resource.
gsl::dyn_array<T>
and gsl::stack_array<T>
are two new array types.
gsl::dyn_array<T>
is a heap-allocated array with a fixed number of elements that is specified at run time.
gsl::stack_array<T>
is a stack-allocated array with a fixed number of elements that is specified at run time.
Thanks to Expects()
and Ensures()
, you can state preconditions and postconditions for your functions. Currently, you have to place them in the function body, but these will be moved in upcoming implementations to the function declaration. Both functions are part of contracts. Appendix C, Contracts, provides more details about contracts.
Here is an example using Expects()
and Ensures()
from the GSL.
int area(int height, int width) { Expects(height > 0); auto res = height * width; Ensures(res > 0); return res; }
When the function invocation breaks the precondition Expects(height > 0)
or the postcondition (Ensures(res >0)
, the program terminates.
gsl::narrow_cast<T>
and gsl::narrow
are two new casts.
gsl::narrow_cast<T>
is a static_cast<T>
that expresses only its intent. A narrowing conversion may happen.
gsl::narrow
is a static_cast<T>
that throws a narrowing_error exception if static_cast<T>(x) != x
.
gsl::not_null<T*>
models a pointer that never should be a null pointer. If you set a gsl::not_null<T*>
pointer to a null pointer, you get a compiler error. You can even put a smart pointer such as std::unique_ptr
or std::shared_ptr
into a gsl::not_null<T*>
. There is one main difference between gsl::not_null<T*>
and a reference: You can rebind a gsl::not_null<T*>
object but not a reference.
Typically, you use gsl::not_null<T*>
for function parameters and their return type. Consequently, you do have to check to see if the pointer is a null pointer.
// p cannot be a null pointer int getLength(gsl::not_null<const char*> p); // p can be a null pointer int getLength(const char* p);
finally
allows you to register a callable that runs at the end of the scope.
void f(int n) { void* p = malloc(1, n); auto _ = finally([p] { free(p); }); ... } // the lambda is invoked
At the end of the function f
, the lambda function [p] { free(p); }
is invoked automatically.
According to the C++ Core Guidelines, you should consider finally
as a last resort if you cannot use proper resource management such as smart pointers or STL containers.