A collection of code snippets, general notes, small pearls of wisdom and bits of knowledge, that may come handy at times.
Don’t expect well thought out descriptions or highly structured content here; these are snippets. (But there are some links spread around for further reading.)
Is a C++ compiler being used?
Check for the predefined macro __cplusplus (two leading underscores!).
It’s defined as an integer literal value when the translation unit is compiled as C++.
When expanded, it denotes the version of the C++ standard that is being used; the currently valid values are:
199711L(until C++11)201103L(C++11)201402L(C++14)201703L(C++17)202002L(C++20)
Usage examples:
-
Bail out if no C++ compiler is used:
#if !defined (__cplusplus) #error C++ compiler required #endif -
Enable features based on the standard version:
#if __cplusplus >= 201402L // ... #elif defined(...) || defined(...) // ... #else //... #endif -
Enclose a code block in extern “C” {…}
(That makes function names in C++ have C linkage (meaning the compiler won’t mangle the name), so that client C code can use the functions by using a C compatible header file that contains just the declaration of the function):#ifdef __cplusplus extern "C" { #endif // ... #ifdef __cplusplus } /* end of the 'extern "C"' block */ #endif
Common Containers
Overview of the most common/usuable/popular container types and some hints on how to choose an apt one.
Further reading:
- Based on I don’t know which container to use (and at this point I’m too afraid to ask)
- Containers library
- Standard Containers
Sequence containers
| Name | #include | Is size fixed? | Will size vary wildly? | Is order important? | Insert/erase at front/back? | Insert/erase in the middle? | Need to find nth element? | Need to merge collections? |
|---|---|---|---|---|---|---|---|---|
std::array |
<array> |
X | - | - | - | - | X | - |
std::vector |
<vector> |
- | - | - | - | - | X | - |
std::list |
<list> |
- | X | - | X | X | - | X |
std::deque |
<deque> |
- | X | - | X | - | X | - |
std::stack |
<stack> |
- | X | X | X | - | - | - |
std::queue |
<queue> |
- | X | X | X | - | - | - |
A few question you should ask yourself:
- Is the size of the container static? Use
std::array. - Will the size of the container vary a lot? This criterion is important for memory allocation.
- Is the order is important?
- Do you need to add/erase values at the extremities of the container (at the beginning or at the end)?
- Do you need to be efficient at inserting/deleting data in the middle of the structure?
- Do you need to find the nth element?
- Do you need to merge collections?
Associative containers
| Name | #include | Has Key/Value association? | Value is key? | Is key unique? | Is ordered by key? | Hashable keys? |
|---|---|---|---|---|---|---|
std::map |
<map> |
X | - | X | X | - |
std::multimap |
<multimap> |
X | - | - | X | - |
std::unordered_map |
<unordered_map> |
X | - | X | - | X |
std::unordered_multimap |
<unordered_multimap> |
X | - | - | - | X |
std::set |
<set> |
- | X | X | X | - |
std::multiset |
<multiset> |
- | X | - | X | - |
std::unordered_set |
<unordered_set> |
- | X | X | - | X |
std::unordered_multiset |
<unordered_multiset> |
- | X | - | - | X |
A few tips:
- There are two kinds of associative arrays: The ones who associate a key with a value (possibly of different types); that’s
std::map.
And the ones for which the key is the value itself; that’sstd::set. - By default, keys are unique (
std::map/std::set); but there is a variant when keys can have multiple entries:std::multimap/std::multiset. - Ordered or not? By default, they are ordered by key; but there is also an
unordered_*version of each.
A Singleton Class
Useful for an object that should/must only exist once in the program (e.g. some random number generator, or some kind of “manager” object):
class Singleton
{
public:
Singleton(Singleton const&) = delete; // Disable copy constructor.
Singleton(Singleton&&) = delete; // Disable move constructor.
Singleton& operator=(Singleton const&) = delete; // Disable copy assignment operator.
Singleton& operator=(Singleton&&) = delete; // Disable move assignment operator.
// [i] Marking functions as deleted is a C++11 feature.
static Singleton& get ()
{
static Singleton instance; // (1) -> see info box -->
return instance;
}
static int Value () { return get().ValueImpl(); }
private:
Singleton() {} // Hide constructor.
int ValueImpl () { return some_value; }
int some_value = 2;
};
And then use it in the other places like this:
#include "Singleton.hpp"
int main ()
{
int x = Singleton::Value();
}
Source: Based on the video “SINGLETONS in C++” by The Cherno.
Identifier (names) reserved by the implementation
In C++, the implementation reserves:
- Any identifier in the global namespace that begins with an underscore (
_). - Any identifier that begins with an underscore (
_) followed by an uppercase letter. - Any identifier that contains a double underscore (
__) in any position.
ISO/IEC 14882:1998 (E) Programming languages - C++
17.4.3.1.2 Global names
- Certain sets of names and function signatures are always reserved to the implementation:
- Each name that contains a double underscore (__) or begins with an underscore followed by an uppercase letter (2.11) is reserved to the implementation for any use.
- Each name that begins with an underscore is reserved to the implementation for use as a name in the global namespace.
{Such names are also reserved in namespace ::std (17.4.3.1).}
- Thus, my personal conclusion and advice:
- Although using an underscore as the first character of an identifier is valid in certain contexts, it is much simpler to stay on track by avoiding a leading underscores at all (instead of trying to remember the exact rules and exceptions).
On binding of const and pointers
Hint: const applies to what is on its left; and if there’s nothing more on the left, it applies to the token on the right.
const char * A;char const * B;- Both declare a pointer to a constant character — the value being pointed at will not change.
char * const C;- Declares a constant pointer to a character — the location stored in the pointer will not change.
const char * const D;- Declares a constant pointer to a constant character — neither the stored location (address), nor the value being pointed at will change.
From my own “Coding Style & Design Guide” page: Prefer the east const style:
Read from right to left [←] to see how it works (you can confirm it with the comment next to it):
int const n; // n is a constant integer
int* p1; // p1 is a (mutable) pointer to a (mutable) integer
int* const p2; // p2 is a constant pointer to a (mutable) integer
int const* p3; // p3 is a (mutable) pointer to a constant integer
int const* const p4; // p4 is a constant pointer to a constant integer
C++ Keywords
ISO C++ 20 keywords (without those that are reserved by a Technical Specification):
alignas alignof and and_eq asm auto
bitand bitor bool break
case catch char
char8_t char16_t char32_t class compl
concept const consteval constexpr constinit const_cast continue co_await co_return co_yield
decltype default delete do double dynamic_cast
else enum explicit
export extern
false float for friend
goto
if inline int
long
mutable
namespace new noexcept not not_eq nullptr
operator or or_eq
private protected public
register reinterpret_cast requires return
short signed sizeof static static_assert static_cast struct switch
template this thread_local throw true try typedef typeid typename
union unsigned using
virtual void volatile
wchar_t while
xor xor_eq
In addition to keywords, there are identifiers with special meaning, which may be used as names of objects or functions, but have special meaning in certain contexts:
final
import
module
override
The following tokens are recognized by the preprocessor when in context of a preprocessor directive:
if elif else endif
ifdef ifndef define undef
include line error pragma
defined __has_include __has_cpp_attribute
export import module
—Source for all: https://en.cppreference.com/w/cpp/keyword
Operator Precedence and Associativity of C++
The precedence rules of a language specify which operator is evaluated first
when two operators with different precedence are adjacent in an expression.
Adjacent operators are seperated by a single operand. The precedence of operators is not
directly specified, but it can be derived from the syntax.
The associatity rules of a language specify which operater is evaluated first when two operators with the same precedence are adjacent in an expression.
Operators in the same segment (on the same level) have equal precedence and are evaluated left to right, unless explicitly forced by parantheses.
The following table is taken from https://en.cppreference.com/w/cpp/language/operator_precedence and is valid for C++20; there are some more details on that page:
Operators are listed top to bottom, in descending precedence:
| Precedence | Operator | Description | Associativity |
|---|---|---|---|
| 1 | :: |
Scope resolution | Left-to-right 🡲 |
| 2 | a++ a-- |
Suffix/postfix increment and decrement | |
type() type{} |
Functional cast | ||
a() |
Function call | ||
a[] |
Subscript | ||
. -> |
Member access | ||
| 3 | ++a --a |
Prefix increment and decrement | Right-to-left 🡰 |
+a -a |
Unary plus and minus | ||
! ~ |
Logical NOT and bitwise NOT | ||
(type) |
C-style cast | ||
*a |
Indirection (dereference) | ||
&a |
Address-of | ||
sizeof |
Size-of[note 1] | ||
| co_await | await-expression(C++20) | ||
new new[] |
Dynamic memory allocation | ||
delete delete[] |
Dynamic memory deallocation | ||
| 4 | .* ->* |
Pointer-to-member | Left-to-right 🡲 |
| 5 | a*b a/b a%b |
Multiplication, division, and remainder | |
| 6 | a+b a-b |
Addition and subtraction | |
| 7 | << >> |
Bitwise left shift and right shift | |
| 8 | <=> |
Three-way comparison operator(since C++20) | |
| 9 | < <= > >= |
For relational operators < and ≤ and > and ≥ respectively | |
| 10 | == != |
For equality operators = and ≠ respectively | |
| 11 | a&b |
Bitwise AND | |
| 12 | ^ |
Bitwise XOR (exclusive or) | |
| 13 | | |
Bitwise OR (inclusive or) | |
| 14 | && |
Logical AND | |
| 15 | || |
Logical OR | |
| 16 | a?b:c |
Ternary conditional[note 2] | Right-to-left 🡰 |
throw |
throw operator | ||
| co_yield | yield-expression(C++20) | ||
= |
Direct assignment (provided by default for C++ classes) | ||
+= -= |
Compound assignment by sum and difference | ||
*= /= %= |
Compound assignment by product, quotient, and remainder | ||
<<= >>= |
Compound assignment by bitwise left shift and right shift | ||
&= ^= |= |
Compound assignment by bitwise AND, XOR, and OR | ||
| 17 | , |
Comma | Left-to-right 🡲 |
- Note 1: The operand of sizeof can’t be a C-style type cast: the expression
sizeof (int) * pis unambiguously interpreted as(sizeof(int)) * p, but notsizeof((int)*p). - Note 2: The expression in the middle of the conditional operator (between
?and:) is parsed as if parenthesized: its precedence relative to?:is ignored.
Additional notes for the operator section
A few grammar rules cannot be expressed in terms of precedence (also known as binding strength) and associatity.
For example,a = b < c ? d=e : f=gmeans(a = b < c) ? (d=e) : (f=g), but you have to look at the grammar to determine that.
— The C++ Programming Language, 3rd Ed.; 6.2 Operator Summary
Implicit conversion
A constructor that takes exactly one argument serves two purposes:
- It can be used as a traditional constructor; or
- it can be used to provide an implicit conversion from the argument type to the constructed type.
To prevent this from happening, use the explicit keyword.
Source: The Old New Thing by Raymond Chen: “Beware the C++ implicit conversion” (with an example).
Initialization values
If an initializer is specified for an object, that initializer determines the initial value of an object.
- If no initializer is specified, a global, namespace, or local static object (⇒ static objects) is initialized to 0 of the appropriate type.
- Local variables (⇒ automatic objects) and objects created on the free store (⇒ dynamic objects/heap objects) are not initialized by default!
- Members of arrays and structures are default initialized or not depending on whether the array or structure is static.
Source: Bjarne Stroustrup, The C++ Programming Language (3rd Ed.), §4.9.5.
References and pointers
From a blog entry by Len Holgate:
A pointer can either point to something, or not, whereas a reference must always refer to something. If you see a pointer in a piece of code you often have to look around a bit to find out if the pointer is valid or if it could be null. If you see a reference you know straight away that it is valid.
That’s pretty much all there is to it. If the code you are writing uses an object that is optional then you might choose to use a pointer to represent that optionality, if the object is not optional and must always be present then you should use a reference.
Using parenthesis with a new statement1
An answer from Brandon Bray (working on the Visual C++ Compiler team) in a forum’s posting:
That’s not entirely true. You’re all correct that putting empty parenthesis after the type in local declaration can be confused with a function prototype, the
newexpression is very different.In general, if the
()are included in anewexpression (i.e. “new T()”), then the item is value intialized (basically the compiler zeros out the memory first).
If the parenthesis are omitted, and the type is a non-POD, item is default initialized.
If the item is POD type, then state of the program is indeterminate (and in some cases the program is ill-formed).So, there’s a performance cost to including the
()in anewexpression, but it makes the program more likely to be correct.More info is in the C++ Standard 5.3.4/15. […]
Sizes of built-in types
C++ guarantees that a char is exactly one byte which is at least 8 bits, short is at least 16 bits,
int is at least 16 bits, and long is at least 32 bits.
It also guarantees the unsigned version of each of these is the same size as the original, for example, sizeof(unsigned short) == sizeof(short).
1 ≡ sizeof(char) ≤ sizeof(short) ≤ sizeof(int) ≤ sizeof(long)
1 ≤ sizeof(bool) ≤ sizeof(long)
sizeof(char) ≤ sizeof(wchar_t) ≤ sizeof(long)
sizeof(float) ≤ sizeof(double) ≤ sizeof(long double)
- sizeof(N) ≡ sizeof(signed N) ≡ sizeof(unsigned N)
- Where N is either
charorshort intorintorlong int.
Multiple variables in a for-loop
Multiple variables in a for-loop are allowed, but only if they’re all of the same type, since the comma in a declaration separates declarators, it doesn’t end a declaration:
for (int i = 0, j = 0; ... ; ++i, ++j) { ... }
Static member variable
Static members need to be defined in the source file, or the linker won’t be able to find them.
Exception: Static, constant, integral members for which one doesn’t take the address.
Most compilers will allow one to initialize them in the class declaration and no corresponding definition is required.
The declaration of a static data member in the member list of a class is not a definition!
You must define the static member outside of the class declaration, in namespace scope:
class X
{
public:
static int i;
};
int X::i = 0; // definition outside class declaration
Once you define a static data member, it exists even though no objects of the static data member’s class exist.
In the above example, no objects of class X exist even though the static data member X::i has been defined.
Source:
- C++ FAQ Lite: 10.10: Why can’t I initialize my static member data in my constructor’s initialization list? ff.,
- IBM: AIX compiler information: Static data members (C++ only)
Type aliases with #define, typedef or using
There are multiple ways on one can employ an type alias (e.g. for shortening a long type expression):
#define
The oldest method is to use #define:
#define ULONG_ALIAS unsigned long
#defineis not a part of the C or C++ language, but a pre-processor macro.- It creates a new name (“ULONG_ALIAS” in the example above) in the source code, that will be replaced with the actual value (“unsigned long” in the example above) by the pre-processor (not by the compiler).
- Problematic: It will only be just a substitute name, without any regard of what the name means in the language.
- Problematic: Nesting
#defines can be painful because of the various syntactic rules for the type names. - Problematic: Scope: A
#defineis valid from the point where it occurs until the end of the translation unit (i.e. file) or until is is#undefined.
Another example:
#define INT_POINTER_ALIAS int *
- INT_POINTER_ALIAS will be an alias for the exact name
int *, i.e. the word int, a space and an asterisk *. - Problematic:
INT_POINTER_ALIAS a, b;will be interpreted asint * a, b;, which will create only a as a pointer to an int, but b as a plain int! - Problematic: INT_POINTER_ALIAS knows nothing about how and where it will be used.
And while this (poor) solution may be fine for a few simple cases, it can also soon became very messy:
Say you want to create an alias ARRAY_POINTER_ALIAS for a pointer to an array of ints.
You cannot simply write a #define for the name only, because there should be text both before and after the variable name; you have to use a parameter, like so:
#define ARRAY_POINTER_ALIAS(a) int (*a)[]
Which could become very cumbersome when you have to take into account multiple variable declarations, sizeofs, and even more complicated things, like further typedef or #define decalrations.
typedef
The next best thing for an alias declaration is using typedef:
typedef int* INT_POINTER_ALIAS;
typedefwill take into account the language’s type system syntax.- INT_POINTER_ALIAS will be an alias directly for the type pointer to an int.
ThusINT_POINTER_ALIAS a, b;will be interpreted asint *a, *b;, which will create both a and b as pointers to ints. typedefcreates a new name for types in the source code itself, not just apply a text replacement.- Note that compared with
#define, the postion/order of the arguments has switched: The alias is on now on the right side. typedefs can also be nested as far as you want, since eachtypedefwill be used as an alias for the type, not for its name.- Scope:
typedefrespects the language’s scope limits,
typedef allows far more flexible use of the type system than #define. To come back to the case from before:
Say you want to create an alias ARRAY_POINTER_ALIAS for a pointer to an array of ints:
typedef int (*ARRAY_POINTER_ALIAS)[];
using
The latest and often recommended way is now to use an alias-declaration with using:
using ulong_alias = unsigned long;
- Note that compared with
typedef, the postion/order of the arguments has switched yet again: The alias is on now on the left side. - Unlike
typedef, the alias-declarations can be templatized. - More details and examples:
Function Objects and Predicates1
Objects that have the operator() defined can be used as functions, and in most cases as replacements for functions.
When the return value for an object’s definition of operator() is a bool (true or false), that object can be used as a predicate.
That is, a predicate is a function that returns true or false.
In C++, a predicate is a special kind of function object. Function objects have a distinct advantage over traditional functions. Because function objects are first and foremost objects, they have all the advantages of objects — class hierarchies, methods, state variables, operators, and so on. They can be passed as objects and held in containers. The state variables of a function object can be accessed before and after they’re used with any container or algorithm.
C++ has a number of built-in function objects and predicates (see Standard Library).
Source: What Are Function Objects and Predicates? (2006).
Access Control: private, protected, public1
- A member (either data member or member function) declared in a private section of a class can only be accessed by member functions and friends of that class.
- A member (either data member or member function) declared in a protected section of a class can only be accessed by member functions and friends of that class, and by member functions and friends of derived classes.
- A member (either data member or member function) declared in a public section of a class can be accessed by anyone.
[TODO] friend? Note that the access control doesn’t hide anything (still visible)
Inheritance rules
Example:
class B { ... };
class D_priv : private B { ... };
class D_prot : protected B { ... };
class D_publ : public B { ... };
class UserClass { B b; ... };
- None of the derived classes can access anything that is private in B.
- In D_priv, the public and protected parts of B are private parts of D_priv.
- In D_prot, the public and protected parts of B are protected parts of D_prot.
- In D_publ, the public parts of B are public in D_publ, but the protected parts of B are also protected in D_publ (i.e. D_publ is-a-kind-of-a B).
- UserClass can access only the public parts of B, which “seals off” UserClass from B.
To make a public member of B so it is public in D_priv or D_prot, state the name of the member with a B:: prefix.
For example, to make member B::f(int,float) public in D_prot, you would say:
class D_prot : protected B
{
public:
using B::f;
};
Sources:
static_assert() and assert()
Assertions are helpful features for testing/debugging and for noticing errors/bugs that should never happen.
- If the expression evaluates to true, the assertion statement does nothing.
- If the conditional expression evaluates to false, an error message is displayed and the program is terminated (→ using
assert) or the compilation fails (→ usingstatic_assert).
An assertion doesn’t allow to recover from an error (which, after all, should never had happenend in the first place).
static_assert()
A keyword/declaration that checks a software assertion at compile-time.
If the specified constant expression is false, the compilation fails with an error and displays the specified message (if one is provided).
static_assert(bce, msg); // Available since C++11.
static_assert(bce); // Since C++17, the message parameter is optional.
- The parameter bce must be an integral constant expression that can be converted to a boolean type (i.e.
trueorfalse) and must be a constant known at compile time. - The parameter msg is the error message that is displayed when the expression parameter is
false.
(Since it has to be a string literal, it cannot contain dynamic information or even a constant expression that is not a string literal itself. In particular, it cannot contain the name of the template type argument.) - Since
static_assert()is a built-in compile-time test, it works regardless whether you build in Release or Debug mode. - One can use the
static_assertkeyword at namespace, class, or block scope.
(Thestatic_assertkeyword is technically a declaration, even though it does not introduce new name into the program, because it can be used at namespace scope.)
assert()
A function-like pre-processor macro that checks a software assertion at run-time.
Evaluates an expression and if the result is false, aborts/terminates the program (and usually prints a diagnostic message).
// #define NDEBUG // uncomment to disable assert()
#include <cassert>
assert(condition);
- The parameter condition must be an expression of scalar type that is interpreted to being either
trueorfalse. - The definition of the macro depends on another macro (
NDEBUG), which is not defined by the standard library:
IfNDEBUGis defined as a macro name at the point in the source code where<cassert>(or<assert.h>) is included, thenassert()is deactivated and does nothing (that is usually the case in Release mode). Most build environments take care of this automatically when you switch between Debug and Release builds.
Important: Because of this volatility, make sure that you do not base the control flow of your code on whetherassert()is enabled or disabled!
There is no standardized interface to add an additional message to assert errors, but there are workarounds:
assert((2 + 2 == 4, "Message")); // -> 1.
assert(condition && "Message"); // -> 2.
- A portable way is to use a comma operator (provided it has not been overloaded).
- Combining a string literal (which always evaluates to true) by logical AND with the condition doesn’t impact the evaluation of the assert.
std::variant
A variant is a data type/class template that can hold values of different types (much like a union in C)
and is available since C++17.
At its core, std::variant is a union of types: It can store one value at a time from a predefined set of types.
Unlike a traditional union, std::variant brings type-safety to the table and keeps track of its active type, ensuring that one accesses the correct value type.
A variant can hold values of various data types, including fundamental types (int, double, etc.), user-defined types (custom classes or structs), and even other variants.
This flexibility and versatility opens up a world of possibilities for handling complex data scenarios; e.g.:
-
Handling multiple data types:
When one works with functions or classes that can accept different data types: Instead of writing multiple overloads, one can use a variant to streamline the code. -
State machines:
Those often require to manage different states and transitions: A variant simplifies this by allowing one to represent states as types and transitions as functions (see).
At any given time, an instance of a variant either holds a value of one of its alternative types, or no value (this state is a bit harder to achieve, see std::monostate below for details).
#include <iostream>
#include <iomanip>
#include <format>
#include <variant> // <-- !
std::variant<std::monostate, int, double, bool, std::string> val;
// | | | | |
// | | | | +- Index 4 }
// | | | +------- Index 3 } For comparison later
// | | +--------------- Index 2 } with the result of
// | +-------------------- Index 1 } val.index()
// +------------------------------------ Index 0 }
val = "String test value";
if (std::holds_alternative<int>(val)) { std::cout << std::format("val is: {0}\n", std::get<int>(val)); }
else if (std::holds_alternative<std::string>(val)) { std::cout << std::format("val is: {0}\n", std::get<std::string>(val)); }
else if (std::holds_alternative<bool>(val)) { std::cout << "val is: " << std::boolalpha << std::get<bool>(val) << '\n'; }
else { std::cout << "..." << '\n'; }
val = 305; // Same variant variable, different type.
auto r = std::get_if<std::string>(&val);
if (r) { std::cout << std::format("val is: {0}\n", *r); }
else { std::cout << "val is: At least not a string this time...\n"; }
val = 1.5;
std::cout << std::format("Current type is the alternative with index {0}\n", val.index());
// Which prints '2' for the index and which checks out (see above).
// 'val' still holds a 'double' value, but we now try to get an 'int' value without checking first;
// that that will raise an exception:
try { std::get<int>(val); }
catch (const std::bad_variant_access& e) { std::cerr << "Error: " << e.what() << std::endl; }
It’s important to check which type is stored in a variant before retrieving the value (use std::holds_alternative or std::get_if for that);
otherwise an exception might get raised/thrown (see example code above).
A few interesting/helpful functions
| Function | (Non)Member | Description |
|---|---|---|
index() |
Member | Returns the zero-based index of the alternative held by the variant. The index is related to the order of alternatives in the variant when it’s initially defined! |
valueless_by_exception() |
Member | Checks if the variant is in the invalid state; returns true or false |
visit... |
Non-Member | ??? Didn’t need (or understand) it yet… (Maybe see and this) |
holds_alternative<T>(V) |
Non-Member | Checks if a variant V currently holds a value of type T; returns true or false. |
get<T>(V) |
Non-Member | Retrieves the value of the given type (or index) T from the variant V; throws on error (std::bad_variant_access). By the way: get returns a reference, so one also can change the value that way! |
get_if<T>(V) |
Non-Member | Returns a (null)pointer to the value of a pointed-to variant V, given the index (or type, if unique) T. |
std::monostate
Tip: Always put a std::monostate as the first entry in a std::variant: Makes the variant default-constructible and makes it also easier to detect an empty state.
The original purpose of std::monostate was to be used as the initial type in a std::variant, to allow it to be default-constructed (which always uses the first alternative) in an empty state;
in case it holds no alternative.
Imagine something like std::variant<A, B>: Maybe there exist no default constructors for type A or B, or they may have unwanted side effects.
What can you do with a monostate? Nothing! Its job is just to be a dummy type that you can use when you are forced to provide a default-constructible type but don’t want to.
In the case of a std::variant, you can think of inserting std::monostate as a way to add an “empty” state to a variant,
saving you the trouble of having to create a std::optional<std::variant<...>>.
You can treat the std::monostate as a dummy state, representing an “empty” or void state.
(Note: There is already a member funktion to check for an “empty” state, known as valueless_by_exception(), but this is “wacky”, so it’s better to avoid it as much as possible.)
Based on Raymond Chen’s What’s the point of std::monostate? You can’t do anything with it! (see also the related discussion thread on Reddit).
More on the topic
- geeksforgeeks: std::variant in C++ 17
- CppStories: Everything You Need to Know About std::variant from C++17
- CppReference: std::variant
- Cengizhan Varli: std::variant in C++
std::optional
The class template std::optional manages an optional contained value (i.e. a value that may or may not be present)
and is available since C++17.
It lets you augment the values of a type T with an additional value of std::nullopt, which represents the absence of a value.
Therefore, a std::optional which holds the value std::nullopt is known as empty (which is also its orginal state when created).
#include <optional>
std::optional<int> var;
if (var == std::nullopt) { /* Do something */ }
The basic operations on std::optional are these (more…):
| Function | Description |
|---|---|
has_value() |
Checking if it holds a value. Returns true or false. |
value() |
Retrieving the value. Throws a std::bad_optional_access exception if the object is empty! |
= |
Assigning a value. |
reset() |
Clearing the value and returning to the empty state. |
Other interesting features:
Contextual conversion
If used in places where the language expects a boolean (if, ||, etc.), a std::optional is considered true if is has any value and false if it is empty.
In short, if (var) is the same as if (var.has_value()).
Note that this does not test whether the wrapped value is true or false:
std::optional<bool> var1 = false;
if (opt1) { /* This executes because the variable is non-empty (even though it is false) */ }
std::optional<void*> var2 = nullptr;
if (opt2) { /* This executes because the variable is non-empty (even though it is nullptr) */ }
This might be confusing to the reader if the value type itself is a bool; in those cases, better be explicit and use if (var1.has_value()).
Equality comparison against a value
An empty std::optional<T> compares unequal to any T.
std::optional<int> var;
if (var == 0) {
// Does NOT execute because the variable is currently empty
// and is therefore not equal to any integer.
}
One can use this instead of the more verbose if (opt.has_value() && opt.value() == 0).
Ordering comparison against a value
An empty std::optional compares less than any non-empty std::optional, and also less than any value.
std::optional<int> var;
if (var > 0) { /* Does NOT execute, because 'empty' is less than all values. */ }
Better to avoid this, except when sorting, because this behavior differs from NaN (another popular “There’s nothing useful here” value) in that the corresponding opposite-sense test does execute:
if (opt <= 0) { /* Executes because 'empty' is always less than any value. */ }
An alternative (but not necessarily a good one, see below):
if (opt.has_value() && *opt > 0) // or...
if (opt.has_value() && *opt < 0)
But those also have a trip wire: Both opt.value() and *opt return the wrapped value, but have different failure modes:
- The explicit
opt.value()call will throw astd::bad_optional_accessexception if the object is empty. - Whereas
*optbypasses the verification and you get the dreaded undefined behavior if the object turns out to be empty after all.
In the above case, you can write the code equivalent as the following, because the compiler can optimize out the redundant emptiness test:
if (opt.has_value() && opt.value() > 0) // or...
if (opt.has_value() && opt.value() < 0)
🡲 Based on: The Old New Thing: Some lesser-known powers of std::optional
Read binary data from a file to a std::vector buffer (and write back)
#include <iostream>
#include <fstream>
std::ifstream infile("C:\\some\\path\\in.dat", std::ios::binary);
infile.seekg (0, infile.end); // Jump to the end of the file.
size_t file_length = infile.tellg(); // Note down that last position as the length.
infile.seekg (0, infile.beg); // Set the read/get pointer back to the beginning.
// The buffer for the file's data.
std::vector<unsigned char> file_data;
file_data.resize(file_length);
// Read all data from the input file into the buffer:
infile.read((char*)file_data.data(), file_data.size());
infile.close();
// Write the data from the buffer to an output file:
std::ofstream outfile;
outfile.open("C:\\some\\path\\out.dat", std::ios::binary);
outfile.write(reinterpret_cast<const char*>(&file_data[0]), file_data.size());
outfile.close();
std::cout << "Input file size: " << file_length << " bytes\n";
std::cout << "File data (buffer) size: " << file_data.size() << " bytes\n";
Exceptions
-
An uncaught exception will cause the runtime to terminate the program; be aware of that when designing your resource handling!
-
What happens if a thrown exception is not caught is specific to the implementation; it bubbles up the stack, but at the very end, only the termination of the program is guaranteed.
Tip: Enclose the main part of the code (in the main() function) in a try/catch clause; that way at least something can be done (or shown: some hint of what went wrong) at the very end (see Rethrow exception below). -
Which brings me to this observation: Nesting try/catch blocks is fine in C++!
-
Not everything that terminates a program can be caught:
If the cause is Undefined Behavior (UB), then trying to catch an exception won’t help. -
The C++ standard library comes with a nice collection of built-in exception objects one can use.
On the other hand: One can throw almost anything: A customized exception, or a custom type, or a fundamental type (likeint,float, or astd::string)… -
One can have multiple
catch()statements after eachtry, so that one can handle different exception types separately.
But note that the order is important: Begin with the most specific, since the first catch that matches will take care of the exception! -
Tip: Always throw by value and catch by const reference (also important if you want to re-throw an exception).
#include <stdexcept>
try
{
int i = 11;
if (i > 10)
throw std::invalid_argument("an invalid value");
if (i < 5)
throw i;
}
catch (const CustomException& e) { std::cerr << "Error: " << e.what() << '\n'; }
catch (const int& e) { std::cerr << "Error: The number too low: " << e << '\n'; }
catch (const std::invalid_argument& e) { std::cerr << "Error: " << e.what() << '\n'; }
catch (const std::range_error& e) { std::cerr << "Error: " << e.what() << '\n'; }
catch (...) { std::cerr << "Error: Unknown exception!\n"; }
// ^^^ Handle any other exception. But here we don't have any details about the exception.
Rethrow exception
By simply using a plain throw, the currently handled exception will be re-thrown (up the stack), as is.
But one can also add information before re-throwing or pick data from the current exception and then throw another exception type.
Tip: Always catch by const reference if you want to re-throw an exception; otherwise the exception may get sliced or become invalid, or… or…
try { /*...*/ }
catch (const std::invalid_argument& e)
{
// 1. Do something here...
// 2. Then let someone higher up the call stack handle the rest...
throw;
}
catch (const int& e)
{
throw runtime_error("Something weird happend in function() when the value was " + string(e));
}
If an exception bubbled up to the very top and was never caught, then only the termination of the program is guaranteed (by the standard), most of the process is optional/implementation-specific.
Even the unwinding of the stack (invoking destructors and cleaning up other stuff) will normally not happen (also because that would destroy data that could help with troubleshooting or debugging); the same goes for displaying a message to the user: Depends on the runtime/operating system.
Tip: Use an enclosing try/catch (all) clause to be on the safe side, like this in main():
int main ()
{
try
{
try
{
/* ... */
}
catch (const int& e)
{
throw std::runtime_error("Something weird happend in function() when the value was " + e);
}
return 0;
}
catch (const std::exception& e)
{
std::cout << "Uncaught exception that bubbled up to the top: " << e.what() << '\n';
return 1;
}
catch (...)
{
std::cout << "Uncaught exception that bubbled up to the top!\n";
return 1;
}
}
Custom exception
class CustomException : public exception
{
public:
CustomException(const string& msg) : message(msg)
{
// ...
}
~CustomException()
{
// ...
}
virtual const char* what() const throw ()
{
return message.c_str();
}
private:
string const message;
};
Remove any white-space characters from a std::string
#include <iostream>
#include <algorithm>
std::string s = "A string with spaces"
s.erase(remove_if(s.begin(), s.end(), isspace), s.end());
std::cout << s << '\n';
Copy (or rather: concatenate) a std::string to a char array
std::string source
char destination[MAX_PATH] = { '\0' };
strncat(destination, source.c_str(), MAX_PATH - strlen(destination));
Measure time duration
auto start = std::chrono::high_resolution_clock::now();
// [The code that should be measured/profiled...]
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
Namespace
👉 Has its own page.
PImpl (Pointer to Implementation)
Which is also known as:
- handle class (Bruce Eckel: Thinking in C++ (2nd Ed))
- opaque pointer (Daniel Sieger: Opaque Pointer Pattern in C++)
- bridge pattern (a design pattern)
- compiler firewall
- cheshire cat (everything about the implementation disappears, except for a single pointer: the “smile”)
- d-pointer (“data pointer”, the name stems from Qt)
Using the PImpl idiom has a few pros and cons:
Advantages:
- Hides the implementation to a client/customer, because the header/declaration then (normally) only has a single pointer as its member;
otherwise the all the members would show up here, even if the weren’t accessible (
private)). - Stable ABI & API and a reduction of re-compilations, because the only member is (usually) a single pointer, which will not change its size, type or position.
Disadvantage:
- Runtime overhead (performance hit), due to a layer of indirection (access, space, lifetime management).
Example
Inspired by a YouTube video from Mike Shah.
Header file (Person.hpp)
The header file only needs a forward declaration and a pointer to that implementation class (which is an incomplete type at this moment, see next paragraph), instead of the actual members. That way, it’s left to the implementation how the class actually stores its data and details about it are hidden.
Important: A destructor must be declared (in the header) and defined (in the implementation), even when using a smart pointer and nothing else happens in the implementation
(could be defaulted: Person::~Person() = default;)! Otherwise a compilation error will occur, because the PImpl class is still an incomplete type.
#include <string>
#include <memory> // for std::unique_ptr
class Person
{
// Using PImpl // Not using PImpl
// -------------------------------//---------------------
public: //
Person (std::string s); //
~Person (); //
std::string GetAttributes (); //
//
private: //
class PImpl; // std::string name;
// std::string height;
PImpl* pimpl; //
// ... or ... //
std::unique_ptr<PImpl> pimpl; //
}; //
Implementation file (Person.cpp)
Since the class Person::PImpl is only defined in the implementation file, one can now change the implementation of the class without affecting the ABI or API (communicated by the header).
#include "Person.hpp"
class Person::PImpl
{
std::string name;
std::string height;
};
Person::Person (std::string s)
{
// Using PImpl // Not using PImpl
// ----------------------------------------//---------------------
pimpl = new PImpl; //
// ... or ... //
pimpl = std::make_unique<PImpl>(); //
//
pimpl->name = s; // name(s);
pimpl->height = "not set"; // height = "not set";
}
Person::~Person ()
{
delete pimpl; // Only needed for a raw pointer, not for a unique_ptr!
}
Person::GetAttributes ()
{
// Using PImpl // Not using PImpl
// ----------------------------------------//---------------------
return pimpl->name + ", " + pimpl->height; // return name + ", " + height;
}
Program file (main.cpp)
#include <iostream>
#include "Person.hpp"
int main ()
{
Person p("Sascha");
std::cout << p.GetAttributes() << std::endl;
return 0;
}
Recommendable resources
Places on the net
- cppreference.com
- cplusplus.com
- C++ Core Guidelines
- C++ FAQ
- C++ FAQ Lite
- C++ By Example
- Compiler Explorer
Books1
- Accelerated C++ (Koenig, Moo)
- The C++ Programming Language (Stroustrup)
- The C++ Standard Library (Josuttis)
- Das C-Grundlagenbuch (Willms; german language; yes, I too started with C before switching to C++)
Film & Television (55)
How To (64)
Journal (17)
Miscellaneous (4)
News & Announcements (21)
On Software (12)
Projects (26)