In the previous article we looked at how to avoid falling into some of the pitfalls of Modern C++. For this article we’ll cover some of the more advanced areas of Modern C++, with explanations of how to avoid problems that novice programmers often encounter.
Continue reading “Common mistakes to avoid in Modern C++ (2)”Author: cpptutor
Common mistakes to avoid in Modern C++ (1)
Learning a new programming language is hard, and is especially daunting when as a novice programmer the compiler rejects pretty much every program you write the first time you try to compile it. Of course, compiler errors and warnings are important as they encourage you to write correct code and ensure that your program actually runs at all, and works as expected.
C++ has traditionally been seen as one of the harder programming languages for beginners to pick up; ideally you should already have some coding experience under your belt in (an)other programming language(s) before trying to learn Modern C++. This mini-series intends to examine the parts of C++ that novice (and more experienced) programmers sometimes fail to understand fully, leading to buggy and/or sub-optimal code.
Continue reading “Common mistakes to avoid in Modern C++ (1)”Generic classes in Modern C++
In this article we’re going to continue the theme of a class-designer’s toolkit, looking at adding generics (template functionality) to C++ classes. Templates have been around since C++98, and almost all C++ programmers will have used them: std::vector<int>
has the type int
as a parameter, itself being a specialization of a generic class. Any user-defined type can be used as a type parameter, having equal status to the built-in types.
Generic classes
Classes which are preceded by a the template
keyword are automatically turned into generic classes. These classes can have any number of template parameters, we’re going to concentrate on classes having just one, this being a template type parameter. The class definition starts of as follows:
A Modern C++ class designer’s toolkit
C++ has promoted class design throughout its history, and Modern C++ continues this tradition. Some new syntax has been added, while other syntax is used less often, or even deprecated. This article aims to summarize best practice using the latest versions of the language in one place, preferring to outline the modern variants (only) without reference to historical style.
Continue reading “A Modern C++ class designer’s toolkit”Applications of C++23’s deducing ‘this’
All C++ programmers should understand the concept of “const
-correctness” which is an additional form of type safety provided by the language. (C99 even incorporated this keyword and feature after C++ introduced it.) A common example of this feature is where classes provide accessors (getters/setters) to member data, with const
member functions for the case where member data is logically immutable. A non-const-correct Matrix
class allowing element access might look as follows:
Visitor Pattern in Modern C++
The Visitor Pattern is a well-known technique of adding specific functionality to a number of possibly unrelated classes without actually modifying them significantly. If you do an online search for examples of this design pattern in C++, you may find examples with an abstract Visitor
base class having a single, overloaded, pure virtual function, often called accept()
. This article explores how to accomplish much the same task using compile-time polymorphism and more recent C++ syntax.
Know ‘constexpr’? Here’s ‘consteval’, ‘constinit’
As you may know, and as described in Chapter 4 of the Guide on this site, functions (and variables) may be declared constexpr
in Modern C++. Functions declared with this keyword are guaranteed to be able to be called at compile-time, returning a result which can be used where a compile-time constant is needed. Importantly, these functions can also be called at with run-time-defined parameters, with the result of the function calculated at run-time with the same logic.
The new keyword consteval
, added in C++20, can be used with function definitions to ensure that evaluation is performed at compile-time only. The restrictions are similar to those for constexpr
functions (global state cannot be modified) with the added restriction that only constant parameter values are passed to the function when it is called. The following function definition is a valid consteval
function:
C++ Iterators Primer
The C++ “Standard Containers” are meant to be accessed through iterators, which are described in the course on this site as pointer-like objects which yield an element when dereferenced. Of course, programmers coming from other language backgrounds sometimes to prefer to use indexing, at least at first, however not all container types offer a subscript operator due to implementation issues (std::list
does not, for example).
The Standard Containers have always provided member functions to provide first and one-past-last elements, however since C++11 non-member functions are also available within the std
namespace, and it is these which allow the functionality for range-for loops, also since C++11. The full list of functions is summarized below:
Uses of ‘auto’ in Modern C++
The auto
keyword has been part of C++ since C++11, and its role has developed somewhat since then. This article intends to cover all the use cases of auto
, including syntax that is new in the upcoming C++23 standard. The list of uses covered is:
- Type deduction for variable definitions
- References to other entities
- Deduction of a function’s return type
- Generic lambdas and functions
- Making a temporary copy of an entity
Modern C++ flow diagnostic tools
Most programs produce output. (I suppose a corner-case could be a unit test framework that indicates all tests passing by not throwing an exception, whilst itself not producing any output.) A usual way of confirming a program is correct is by examining its output, this is familiar to programmers as part of the edit-compile-run-debug cycle of coding practice. (Note: I’m not including coding practices such as TDD, which impose a slightly different workflow.)
So, hands up, who’s ever done it? Of course, I refer to inserting debugging code such as std::cerr << "here1\n";
into C++ source files, when you want to ensure a certain code path has been visited. This article intends to show a way for you to never have to use such practices again, thereby avoiding the risk of evidence of debugging hacks remaining in your production code.
C++ Coroutines Primer (3)
In the previous two articles of this mini-series we introduced co_await
, co_yield
and co_return
, the last of these not taking a parameter. In this article we’ll look at the possibility of co_return
actually returning a value, and the minimal boilerplate necessary to allow this functionality to work. Note that this boilerplate does become closer in complexity to Generator.hpp
from the first article, so in your own projects you may prefer to start with that source file and adapt it as necessary.
C++ Coroutines Primer (2)
In the previous article we looked at co_yield
producing a result from a coroutine and co_return
(without parameter) causing an early “return” from a coroutine. In this article we’re going to look at the third C++ coroutine keyword, being co_await
, and introduce the C++ boilerplate code necessary to allow it to function.
While co_await
and co_yield
are closely related, the semantics and meaning are slightly different: co_await
says “suspend the current coroutine function until further notice, then resume at this point when directed by the caller“, while co_yield
says “return this value to the caller right away, then resume at this point when another value is requested“. When control flow through a coroutine arrives at co_await a;
, where a
is an object we’ll call a “coroutine context”, a number of steps take place:
C++ Coroutines Primer (1)
Coroutines are a major addition to the C++20 language standard, and so should be interesting to anyone wishing to develop their use of Modern C++. Coroutines are different to threads (which have been standardized since C++11) and are considerably more lightweight in their Standard Library implementation, requiring just the <coroutine>
header. An imperfect analogy could be that threads are similar to pre-emptive multitasking, while coroutines tend to mirror co-operative multitasking. Coroutines are not a replacement for threads, and the two can usefully co-exist; a coroutine can be paused on one thread and re-started on another, for example. An attractive feature of coroutines is that they are less susceptible to data races, deadlock, and some of the other pitfalls when using threads.
So the down side? While the change to the language itself is only the addition of three new keywords, co_await
, co_yield
and co_return
, a large amount of boilerplate (even by C++ standards) is needed to allow them to do anything useful. There is a three-way interaction between the language keywords used in your coroutine code, the Standard Library implementation, and the boilerplate they require as the “glue” between them. This article intends to cover co_yield
, with a focus on getting it to act like Python’s yield
keyword when constructing generators.
C++ Lambdas Primer (4)
So far in this mini-series we’ve looked at capture by reference using [&]
, however you should know that there is another way of accessing variables from the enclosing scope called capture by copy, and this uses the syntax [=]
. Can you guess the output of the program shown below?
#include <iostream>
auto f() {
int a{ 1 };
auto l = [=]{ std::cout << "l(): a = " << a << '\n'; };
a = 2;
l();
a = 3;
l();
a = 4;
return l;
}
int main() {
f()();
}
C++ Lambdas Primer (3)
Often it is desirable for a lambda function to read or modify state which has to be preserved between calls. Just about the simplest example of a stateful lambda is one that adds up all the numbers it is called with, as shown in this example program:
#include <algorithm>
#include <iostream>
int main() {
auto data = { 1.0, 2.2, 3.1, 2.1, 0.5, 1.6 };
double sum{};
auto sum_and_average = [&](const auto& elem){
std::cout << elem << " ";
sum += elem;
};
std::for_each(begin(data), end(data), sum_and_average);
std::cout << "\nSum: " << sum << '\n';
}