CppCoreGuidelines Documentation

The C++ Core Guidelines are a set of tried-and-true guidelines, rules, and best practices about coding in C++

45,233 stars CSS CSS 1 file Β· ~4,789 tokens
RAW Doc

File: README.md

"Within C++ is a smaller, simpler, safer language struggling to get out."
-- Bjarne Stroustrup

The C++ Core Guidelines are a collaborative effort led by Bjarne Stroustrup, much like the C++ language itself. They are the result of many
person-years of discussion and design across a number of organizations. Their design encourages general applicability and broad adoption but
they can be freely copied and modified to meet your organization's needs.

Getting started

The guidelines themselves are found at CppCoreGuidelines. The document is in GH-flavored MarkDown. It is intentionally kept simple, mostly in ASCII, to allow automatic post-processing such as language translation and reformatting. The editors maintain one
version formatted for browsing. Note that it is manually integrated and can be slightly older than the version in the master branch.

The Guidelines are a constantly evolving document without a strict "release" cadence. Bjarne Stroustrup periodically reviews the document and increments the version number in the introduction. Checkins that increment the version number are tagged in git.

Many of the guidelines make use of the header-only Guidelines Support Library. One implementation is available at GSL: Guidelines Support Library.

Background and scope

The aim of the guidelines is to help people to use modern C++ effectively. By "modern C++" we mean C++11 and newer. In other
words, what would you like your code to look like in 5 years' time, given that you can start now? In 10 years' time?

The guidelines are focused on relatively higher-level issues, such as interfaces, resource management, memory management, and concurrency. Such
rules affect application architecture and library design. Following the rules will lead to code that is statically type-safe, has no resource
leaks, and catches many more programming logic errors than is common in code today. And it will run fast -- you can afford to do things right.

We are less concerned with low-level issues, such as naming conventions and indentation style. However, no topic that can help a programmer is
out of bounds.

Our initial set of rules emphasizes safety (of various forms) and simplicity. They may very well be too strict. We expect to have to introduce
more exceptions to better accommodate real-world needs. We also need more rules.

You will find some of the rules contrary to your expectations or even contrary to your experience. If we haven't suggested that you change your
coding style in any way, we have failed! Please try to verify or disprove rules! In particular, we'd really like to have some of our rules
backed up with measurements or better examples.

You will find some of the rules obvious or even trivial. Please remember that one purpose of a guideline is to help someone who is less
experienced or coming from a different background or language to get up to speed.

The rules are designed to be supported by an analysis tool. Violations of rules will be flagged with references (or links) to the relevant rule.
We do not expect you to memorize all the rules before trying to write code.

The rules are meant for gradual introduction into a code base. We plan to build tools for that and hope others will too.

Contributions and LICENSE

Comments and suggestions for improvements are most welcome. We plan to modify and extend this document as our understanding improves and the
language and the set of available libraries improve. More details are found at CONTRIBUTING and LICENSE.

Thanks to DigitalOcean for hosting the Standard C++ Foundation website.


File: docs/dyn_array.md

`gsl::dyn_array`

gsl::dyn_array is a dynamic array that is intended to be a replacement for slinging
around raw pointers and sizes. Notably, it owns all of its elements. It should replace
the following idioms:

  • Replace new T[n] with gsl::dyn_array<T>(n).
  • Replace both foo(T*, size_t) and foo(unique_ptr<T[]>&, size_t) with
    foo(gsl::dyn_array<T>&).

It can be thought of like a...

  • std::array except the size is specified at runtime.
  • std::vector except it can neither shrink nor grow.

By design, gsl::dyn_array is not a Container as defined by the C++ Named
Requirements because we want to avoid the invalidation of iterators or references to
gsl::dyn_array objects. Furthermore, by design, there is no support for operations
(other than destruction) that can invalidate iterators or pointers into the sequence
owned by a gsl::dyna_array object. An gsl::dyn_array object cannot be moved,
nor can it be copied.

Construction

gsl::dyn_arrays can be constructed in the following ways:

  • Default construct a dyn_array with no elements:
c++
constexpr dyn_array();
  • Move construct a dyn_array from other: not possible
c++
dyn_array(dyn_array&& other) = delete;
  • Construct a dyn_array with n default constructed elements:
c++
constexpr explicit dyn_array(size_t n, const Allocator & alloc = Allocator());
  • Construct a dyn_array with n elements, each copy constructed from arg:
c++
constexpr dyn_array(size_t n, const T& arg, const Allocator & alloc = Allocator());
  • Construct a dyn_array with elements from the range [first, last):
c++
template <typename InputIt>
#ifdef __cpp_lib_concepts
    requires(std::input_iterator)
#endif /* __cpp_lib_concepts */
constexpr dyn_array(InputIt first, InputIt last, const Allocator & alloc = Allocator());
  • Construct a dyn_array from a range:
c++
#ifdef __cpp_lib_containers_range
template <typename R>
    requires(std::ranges::range<R>)
constexpr dyn_array(std::from_range_t, R&& r, const Allocator & alloc = Allocator());
#endif /* __cpp_lib_containers_range */
  • Construct a dyn_array with elements from the initializer list:
c++
constexpr dyn_array(std::initializer_list<T>, const Allocator & alloc = Allocator());

Operations

In addition to the operations required by the named requirements, gsl::dyn_array will
support the following operations:

  • Access the specified element with bounds checking:
c++
constexpr T& operator[](size_t);
constexpr const T& operator[](size_t) const;
  • Access the underlying array:
c++
constexpr T* data() noexcept;
constexpr const T* data() const noexcept;
  • Return the number of elements in the dyn_array:
c++
constexpr size_t size() const noexcept;

FAQ

Why no push_back (and friends)?

gsl::dyn_array is intended to be a fixed-size array and all objects should be
constructed at creation. It supports no iterator/pointer-invalidating operation.

Why does `gsl::dyn_array` not conform to the `Container` Named Requirements?

gsl::dyn_array is intended to be a safer replacement for raw pointers and sizes. We
don't want users to accidentally use it in a way that would be unsafe. For example,
gsl::dyn_array does not have copy or move operations. This is because it
would be possible to invalidate existing iterators and references.

Bounds Checking Semantics

If an out-of-bounds access (read or write) is attempted, gsl::dyn_array should follow
the contract violation strategy outlined in GSL.assert: Assertions.

References


File: docs/gsl-intro.md

Using the Guidelines Support Library (GSL): A Tutorial and FAQ

by Herb Sutter

updated 2018-01-08

Overview: "Is this document a tutorial or a FAQ?"

It aims to be both:

  • a tutorial you can read in order, following a similar style as the introduction of K&R by building up examples of increasing complexity; and

  • a FAQ you can use as a reference, with each section showing the answer to a specific question.

Motivation: "Why would I use GSL, and where can I get it?"

First look at the C++ Core Guidelines; this is a support library for that document. Select a set of guidelines you want to adopt, then bring in the GSL as directed by those guidelines.

You can try out the examples in this document on all major compilers and platforms using this GSL reference implementation.

gsl::span: "What is gsl::span, and what is it for?"

gsl::span is a replacement for (pointer, length) pairs to refer to a sequence of contiguous objects. It can be thought of as a pointer to an array, but that knows its bounds.

For example, a span refers to a sequence of seven contiguous integers.

A span does not own the elements it points to. It is not a container like an array or a vector, it is a view into the contents of such a container.

span parameters: "How should I choose between span and traditional (ptr, length) parameters?"

In new code, prefer the bounds-checkable span<T> instead of separate pointer and length parameters. In older code, adopt span where reasonable as you maintain the code.

A function that takes a pointer to an array and a separate length, such as:

cpp
// Error-prone: Process n contiguous ints starting at *p
void dangerous_process_ints(const int* p, size_t n);

is error-prone and difficult to use correctly:

cpp
int a[100];
dangerous_process_ints(a, 1000); // oops: buffer overflow

vector v(200);
dangerous_process_ints(v.data(), 1000); // oops: buffer overflow

auto remainder = find(v.begin(), v.end(), some_value);
    // now call dangerous_process_ints() to fill the rest of the container from *remainder to the end
dangerous_process_ints(&*remainder, v.end() - remainder); // correct but convoluted

Instead, using span encapsulates the pointer and the length:

cpp
// BETTER: Read s.size() contiguous ints starting at s[0]
void process_ints(span<const int> s);

which makes process_ints easier to use correctly because it conveniently deduces from common types:

cpp
int a[100];
process_ints(a); // deduces correct length: 100 (constructs the span from a container)

vector v(200);
process_ints(v); // deduces correct length: 200 (constructs the span from a container)

and conveniently supports modern C++ argument initialization when the calling code does have distinct pointer and length arguments:

cpp
auto remainder = find(v.begin(), v.end(), some_value);
    // now call process_ints() to fill the rest of the container from *remainder to the end
process_ints({remainder, v.end()}); // correct and clear (constructs the span from an iterator pair)

Things to remember

  • Prefer span instead of (pointer, length) pairs.
  • Pass a span like a pointer (i.e., by value for "in" parameters). Treat it like a pointer range.

span and const: "What's the difference between `span` and `const span`?"

span<const T> means that the T objects are read-only. Prefer this by default, especially as a parameter, if you don't need to modify the Ts.

const span<T> means that the span itself can't be made to point at a different target.

const span<const T> means both.

Things to remember

  • Prefer a span<const T> by default to denote that the contents are read-only, unless you do need read-write access.

Iteration: "How do I iterate over a span?"

A span is an encapsulated range, and so can be visited using a range-based for loop.

Consider the implementation of a function like the process_ints that we saw in an earlier example. Visiting every object using a (pointer, length) pair requires an explicit index:

cpp
void dangerous_process_ints(int* p, size_t n) {
    for (auto i = 0; i < n; ++i) {
        p[i] = next_character();
    }
}

A span supports range-for -- note this is zero-overhead and does not need to perform any range check, because the range-for loop is known by construction not to exceed the range's bounds:

cpp
void process_ints(span s) {
    for (auto& c : s) {
        c = next_character();
    }
}

A span also supports normal iteration using .begin() and .end().

Note that you cannot compare iterators from different spans, even if they refer to the same array.

An iterator is valid as long as the span that it is iterating over exists.

Element access: "How do I access a single element in a span?"

Use myspan[offset] to subscript, or equivalently use iter + offset wheren iter is a span<T>::iterator. Both are range-checked.

Sub-spans: "What if I need a subrange of a span?"

To refer to a sub-span, use first, last, or subspan.

cpp
void process_ints(span<widget> s) {
    if (s.length() > 10) {
        read_header(s.first(10));   // first 10 entries
        read_rest(s.subspan(10));   // remaining entries
        // ...
    }
}

In rarer cases, when you know the number of elements at compile time and want to enable constexpr use of span, you can pass the length of the sub-span as a template argument:

cpp
constexpr int process_ints(span<widget> s) {
    if (s.length() > 10) {
        read_header(s.first<10>());   // first 10 entries
        read_rest(s.subspan<10>());   // remaining entries
        // ...
    }
    return s.size();
}

span and STL: "How do I pass a span to an STL-style [begin,end) function?"

Use span::iterators. A span is iterable like any STL range.

To call an STL [begin,end)-style interface, use begin and end by default, or other valid iterators if you don't want to pass the whole range:

cpp
void f(span<widget> s) {
    // ... 
    auto found = find_if(s.begin(), s.end(), some_value);
    // ... 
}

If you are using a range-based algorithm such as from Range-V3, you can use a span as a range directly:

cpp
void f(span<widget> s) {
    // ... 
    auto found = find_if(s, some_value); 
    // ... 
}

Comparison: "When I compare `span`s, do I compare the `T` values or the underlying pointers?"

Comparing two span<T>s compares the T values. To compare two spans for identity, to see if they're pointing to the same thing, use .data().

cpp
int a[] = { 1, 2, 3};
span sa{a};

vector v = { 1, 2, 3 };
span sv{v};

assert(sa == sv); // sa and sv both point to contiguous ints with values 1, 2, 3
assert(sa.data() != sv.data()); // but sa and sv point to different memory areas

Things to remember

  • Comparing spans compares their contents, not whether they point to the same location.

Empty vs null: "Do I have to explicitly check whether a span is null?"

Usually not, because the thing you usually want to check for is that the span is not empty, which means its size is not zero. It's safe to test the size of a span even if it's null.

Remember that the following all have identical meaning for a span s:

  • !s.empty()
  • s.size() != 0
  • s.data() != nullptr && s.size() != 0 (the first condition is actually redundant)

The following is also functionally equivalent as it just tests whether there are zero elements:

  • s != nullptr (compares s against a null-constructed empty span)

For example:

cpp
void f(span<const int> s) {
    if (s != nullptr && s.size() > 0) { // bad: redundant, overkill
        // ...
    }

    if (s.size() > 0) { // good: not redundant
        // ...
    }

    if (!s.empty()) { // good: same as "s.size() > 0"
        // ...
    }
}

Things to remember

  • Usually you shouldn't check for a null span. For a span s, if you're comparing s != nullptr or s.data() != nullptr, check to make sure you shouldn't just be asking !s.empty().

as_bytes: "Why would I convert a span to `span`?"

Because it's a type-safe way to get a read-only view of the objects' bytes.

Without span, to view the bytes of an object requires writing a brittle cast:

cpp
void serialize(char* p, int length); // bad: forgot const

void f(widget* p, int length) {
    // serialize one object's bytes (incl. padding)
    serialize(p, 1); // bad: copies just the first byte, forgot sizeof(widget)
}

With span the code is safer and cleaner:

cpp
void serialize(span<const byte>); // can't forget const, the first test call site won't compile

void f(span<widget> s) {
    // ...
    // serialize one object's bytes (incl. padding)
    serialize(as_bytes(s)); // ok
}

Also, span<T> lets you distinguish between .size() and .size_bytes(); make use of that distinction instead of multiplying by sizeof(T).

Things to remember

  • Prefer span<T>'s .size_bytes() instead of .size() * sizeof(T).

These are not directly related to span but can often come up while using span.

  • Use byte everywhere you are handling memory (as opposed to characters or integers). That is, when accessing a chunk of raw memory, use gsl::span<std::byte>.

  • Use narrow() when you cannot afford to be surprised by a value change during conversion to a smaller range. This includes going between a signed span size or index and an unsigned today's-STL-container .size(), though the span constructors from containers nicely encapsulate many of these conversions.

  • Similarly, use narrow_cast() when you are sure you won’t be surprised by a value change during conversion to a smaller range


File: talks/README.md

The guidelines were introduced during a number of talks at CppCon 2015.
Here are video recordings of those talks:

The YouTube channel for CppCon is here.

Slides from the talks are available in this folder.