Programmer's Wiki
Advertisement

This C++ wiki page provides a hierarchical listing of topics about the C++ programming language.

  • Overview
    Like many programming languages, C++ uses a "variable" to name a memory location which can store some value. A running C++ program executes an assignment statement iX=1; to assign value 1 into variable iX. But first, each variable has a "type" (its way of using its memory bits to represent its values, such as int) which must be specified by the programmer "defining" that variable within some "scope". Assignment is only one example of a statement; a sequence of statements can be grouped into a code block (written within braces). The execution of that code block can be conditional, depending on the result of testing a "relational expression" (e.g., the equality of two sub-expressions in if (iX + iY == 33) {block}). Generally, a program's execution linearly flows from first statement to last statement, but that program flow can jump around as controlled by loops or by "calling a function". Information can be "passed" into the function (via its "parameters"), and/or returned from the function (by a return 123; statement). C++ enables object-oriented programming (OOP), where an "object" is an instance of a type whose data is a combination of "data members" (as had already been listed in some "class" definition). That class definition also describes "member functions", whose usage is designed to manipulate a specific instance of those data members (i.e., to manipulate a specific object).
  • Preprocessing
    • Macros
      A "preprocessor macro" enables the effect of cut-and-paste of source code, from that macro's definition (in a #define statement) to anywhere the programmer "expands" that macro. This cutting-and-pasting action could be parameterized. If a macro has been defined to accept a parameter, its expansion is allowed to pass nothingness. A "variadic macro" is written to accept a variable number of macro-parameters.
    • Tokenization
      When your C++ source file is being compiled, one of the first things that happens is your compiler uses its preprocessor to "tokenize" your program. Tokenization would process code like iA+++iB by splitting it into four tokens (variable identifier iA, then operator ++, then operator +, then variable identifier iB). A preprocessor macro is able to use ## within its definition, to construct a compilation-token while the macro expands.
    • Preprocessing Strings
      Two adjacent string literals are treated as if they had been concatenated. For a string literal that contains a special character (e.g., a newline), either escape that special character (\n) or use a raw-string-literal. A preprocessor macro can optionally add quotation marks to a macro-parameter while that macro expands.
    • Compile-time Constants
      In C++ syntax, there are some quantities that must be known while your compiler is compiling. In those situations, you could use expression 1+2 (because the compiler is able to perform arithmetic while it's compiling), but function calling is too complex of an activity to occur during compilation (unless that function's features are radically stripped-down). Use keyword constexpr to mark a stripped-down function that might be "called" during compilation, or a variable which might be "evaluated" during compilation.
    • Compile-time Assertions
      The traditional way to assert the truth of something at compile-time was to use the preprocessor's #error (within some #if block). But some compile-time activity happens after the preprocessor has finished, so modern code uses a static_assert() compile-time assertion.
    • Conditional-Compilation
      The traditional way to conditionally-compile code was to use #ifdef or #if. But the preprocessor cannot reason about types, so modern code sometimes uses if constexpr instead.
  • Namespace
    • Namespace Shortcuts
      Anything defined with namespace NRNamespace {...} will require additional qualification (writing variable giVarB as NRNamespace::giVarB) unless you have requested to skip that requirement on a case-by-case basis (programming using NRNamespace::giVarB;). Or, the qualification requirement could be skipped for an entire namespace all at once.
    • Nesting
      Namespaces can be nested, with the qualification programmed as NROuter::NRInner::giVar.
    • Splitting across Files
      A single namespace's location can be split, possibly across multiple files.
    • Named and Unnamed
      An unnamed namespace {...} is a way to restrict the visibility of a global variable to one single file. If you dislike the name that somebody gave to a namespace, namespace NRNew = NROld; creates your own alias for it.
  • Type
    • Hungarian Prefixes
      C++'s signed integer types are signed char, short, int, and long long. On Win32, the sizes of those are 1, 2, 4, and 8 bytes (respectively), but that might be different on other platforms. Each of those types has an unsigned version (e.g., unsigned int instead of int). C++'s floating-point types are float and double. On Win32, the sizes of those are 4 and 8 bytes (respectively). A popular programming practice is to name your variables using a "Hungarian prefix", where its name's first few letters is an encoding of that type (e.g., variable fVar has type float), but the language makes no such requirement.
    • Boolean
      C++'s primitive boolean type is bool. It has values true and false.
    • Literals
      • Financial 1000-Markers
        A number like one million cannot be programmed as 1,000,000 because the comma is used for other purposes. But you can write a numeric literal as 1'000'000.
      • Binary and Hexadecimal
        The number thirteen can be written in decimal (13), hexadecimal (0x0D), octal (015), or binary (0B1101).
      • Custom Literals
        You can now define your own custom syntax for literals of the form prefix_suffix. After programming int operator"" _meter(), you can use 123_meter in any situation where you used to program with an ordinary numeral.
    • Bitwise Representation
      • Ordering of Bytes
        All CPUs are either "little-endian" (the least-significant bytes occur first in the internal representation of an integer) or "big-endian" (the opposite order).
      • Negative Value
        The negative value of an integer could be stored as either "2's complement" (-1 viewed as unsigned char is 0B11111111) or "1's complement" (it is 0B11111110).
      • Bit Shifting
        A value's bits could be shifted leftwards or rightwards with << or >> operators.
      • Non-char Byte
        A std::byte variable is like a unsigned char variable, except that it can only be used for bitwise operations (not for character nor numeric operations).
      • Bit Flags
        A variable whose type is std::bitset<5> stores five bits (five flags), which are individually accessed using array-indexing notation.
    • Max Value
      • Overflow
        On Win32, the max value that can be stored in a unsigned int variable is 0xFFFF'FFFF. If you further increment that variable, it will "overflow" (wrapping around back to 0x0000'0000 without error or warning).
      • INT_MAX
        In C, macros like LONG_MIN and LONG_MAX had been defined to be the smallest and largest values that could be represented in that particular type. C++'s modernization of INT_MAX is std::numeric_limits<int>::max().
    • Conversion
      • Promoting Small Integers
        Often, there are no machine-language mathematical operators for integers smaller than int. Consequently, the compilation result of bTermA + bTermB where bTermA and bTermB are both unsigned char will "promote" both of these two terms into int before doing the addition.
      • Coercion
        A math operation can only be applied to two operands of the same type. If they are not already of the same type, then one of them will need to be "coerced" (if possible, the compiler automatically inserts an implicit type-conversion that changes a value's representation from one type to another).
      • Casting
        A "type cast" is a manually-programmed expression of type-conversion. Traditional C programs wrote casting as (float)iVar. C++ programs write casting as static_cast<float>(iVar). When casting from a pointer-to-base-class into a pointer-to-derived-class, using dynamic_cast<> instead of static_cast<> adds a runtime check which returns NULL if the pointed-at object wasn't actually an instance of that derived class.
    • Enumeration
      A traditional enumeration was written as enum ERType {ZERO=0, ONE, TWO};, but modern code writes it as enum class ERType {...};. In the modern way, values must be fully-qualified as ERType::TWO when they are used.
    • Shortcuts
      • Templated typedef
        A "type alias" lets you use a new name for an existing type. Traditionally, this was programmed as typedef int INTEGER_VIA_TYPEDEF;. In modern C++, this is programmed as using INTEGER_VIA_USING = int;. Only the modern syntax can be templated.
      • From Another Variable
        If variable iOtherVar has already been defined to have type int, then definition decltype(iOtherVar) iVar = 22; is the same as defining int iVar = 22;. This decltype() shortcut can even refer to a full expression.
      • From Initialization
        The compiler can infer a variable's type from its initialization data. So defining auto iVar = 11; is the same as defining int iVar = 11; (because 11 is a int literal).
      • From a return Statement
        A compiler can infer a function's return type from its return statement, by specifying the return type as either auto or decltype(auto).
      • Trailing Return Type
        In templated code (using template-parameter T), if you want your compiler to infer a function's return type from an expression that involves a parameter's type, then use this "trailing return type" syntax: auto AddTwo(T x) -> decltype(x+2) { return x+2; }.
    • Variant
      • Any-Variant
        A variable xVar whose type is std::any can store values of differing types at different points in time. After assigning xVar = 1.1F;, that float value can be accessed by std::any_cast<float>(xVar);. An uninitialized any-variable will be in a special "no value" state that will be reported by xVar.has_value().
      • Templated-Variant
        A variable xVar whose type is std::variant<int,float,std::string> can store a value that is either int or float or std::string (only one of those options at a time). Thus, this templated-variant variable is a modern alternative to a C union. After assigning xVar = 9.9F;, you know that the variable is currently storing a float because std::holds_alternative<float>(xVar) returns true and xVar.index() returns 1 (0-based indexing of type options). Access that value by either std::get<float>(xVar) or by std::get<1>(xVar).
      • Optional-Value
        A variable whose type is std::optional<int> is similar to a pointer to int in the sense that its value might be std::nullopt (similar to a null pointer). In that case, we understand it to semantically mean that the variable "has no meaningful value". Otherwise, an int can be stored by assignment, and accessed by dereferencing.
    • RTTI: RunTime Type Information
      The C++ standard provides rather little support for RTTI. You can call typeid(int), which returns a const std::type_info& that can be equated (using ==) or converted into string (using .name()).
  • Variables
    • Lifetime and Scope
      A variable defined outside of all functions will have "static-lifetime" (it will exist throughout your software's entire duration). A variable defined inside some function could also have static-lifetime, but it would need to be marked by the static keyword. Otherwise, it would be a transient "local-variable" whose storage will disappear when that function ends. A variable defined inside some function has "local-scope" (it can only be used from within that function, regardless of its lifetime). A variable defined outside of all functions has "global-scope" (it can be used by all subsequent functions). If you mark a globally-scoped variable by the static keyword, then it can only be used in this source file.
    • Initialization
      A constant variable (marked by the const keyword) must be initialized. If a static-lifetime variable is not manually initialized, then it will be automatically initialized to 0. If a local object variable is not manually initialized, then its default-constructor will construct it. If a local primitive variable is not manually initialized, then its value will be undefined until something gets assigned to it.
    • Uniform Initialization
      There are three different syntax that you could use for initializing a variable: traditional "implicit initialization" (int iVar = 11;), traditional "explicit initialization" (int iVar(11);), and modern "uniform initialization" (int iVar {11};).
    • Initialize without Constructor
      Traditionally, data members were initialized by passing parameters to a custom constructor, which would copy those parameterized values to the data members. In modern C++, you also have the option of just using the default constructor, and specifying those values as an initialization list. The items from that brace-enclosed list will be given to data members based on the order of members within the class definition.
    • Decomposing-Auto
      The statement auto[iDesti,fDesti] = oSource; simultaneously defines two variables (iDesti and fDesti), initializing each of them to some corresponding value from the right-hand-side. The right-hand-side could be a std::tuple<int,float> or it could be an object (whose first data member is a int, and second data member is a float).
    • Define at Same Time as Type
      Programmers usually define a class (class CRType {...};) separately from using that class' name in an object's definition (a subsequent CRType oObject; statement). However, it is possible to combine these into a single codeline (class CRType {...} oObject;).
  • Indirection
    A "pointer" is a variable whose value is a memory address. If iVar has already been defined as a int variable, defining int* piVar = &iVar; will use the "address-of" operator (&) to obtain the address where iVar has been stored in memory — that memory address will get stored in pointer variable piVar. On the other hand, a "reference" (int& riVar = iVar;) doesn't create any additional storage; instead, it specifies a new name (riVar) which now also refers to that same storage location that already existed for iVar.
    • Pointers
      Evaluating the name of a pointer variable (piVar) produces a memory address. Evaluating the "dereferencing" of that pointer variable (*piVar) produces whatever value had been stored in memory at that memory address.
    • Typed NULL
      The traditional NULL macro was really just a int with value 0. This is commonly used as a special value assigned to a pointer variable, having the semantic meaning of "not pointing at anything" (because computers will never try to use their memory at address 0). Modern C++ uses nullptr instead of NULL, because nullptr has a dedicated std::nullptr_t type.
    • Arrays
      The definition int piArray[3] = {0, 11, 22}; defines an "array" variable. In memory, space is reserved for three integers (contiguously). The array variable piArray is equivalent to a pointer which is pointing at the first of those three integers.
    • Reference Variables
      A reference (int& riVar = iVar;) must always be initialized to some kind of existing storage, and the location of that storage cannot be changed. However, the value stored at that location can be changed, so subsequently doing riVar = 99; will cause iVar to now contain 99.
    • Rvalue-References
      • Move-Semantics
        Traditionally, a class could provide a "copy-constructor" CRType::CRType(const CRType& x) {...} and a "copy-assignment operator" CRType& CRType::operator=(const CRType& x) {...}. These would get used in several situations, such as the definition of oDesti by CRType oDesti(oSource); (where oSource had already been defined as a CRType object). If CRType is some kind of custom container, then these two functions are usually implemented by deeply copying all contained items, which might be computationally-expensive. In modern software, these are supplemented by also programming "move-constructor" and "move-assignment operator" functions that are implemented by only transferring a payload handle. Function overloading can distinguish between move-constructor and copy-constructor, because the move-constructor parameter's type is a "rvalue-reference" CRType&&.
      • Moving from this
        A function like AssignTo() is the opposite of a common assignment operator (assigning from this object to the parameter, instead of from the parameter to this object). To specify function overloading that distinguishes between move-semantics and copy-semantics, a function like AssignTo() would program either & or && immediately before the function body's opening curly-brace.
      • Perfect Forwarding
        If both move-semantics and copy-semantics exist in both a base class and a derived class, then you might need to maintain the choice of move-or-copy when a derived class' function delegates up to the base class' function. To maintain that move-or-copy choice, std::forward<> is used.
    • Smart Pointers
      • Risk from Raw Pointers
        When implementing one of your functions, you might want to dynamically allocate memory (using CRType* poRaw = new CRType;) which you intend to deallocate at the end of your function (using delete poRaw;). But this will leak memory if an unhandled exception gets thrown during the middle of your function. Instead of your local variable's type being a raw pointer, it could be a local object variable (initialized as CRSmart poSmart(new CRType);). Whenever poSmart goes out of scope (even if because of an exception), that local object's destructor will get called. So we can move the responsibility of doing delete from our manually-programmed function, into the destructor of this CRSmart "smart pointer" class. C++ provides several built-in smart pointer classes that work like this.
      • Unique Ownership
        If a smart pointer's memory should be "owned" by exactly one client, use std::unique_ptr<> for the smart pointer type.
      • Shared Ownership
        Multiple std::shared_ptr<> variables can collectively manage a shared memory allocation, due to built-in reference-counting. C++ will automatically call delete on the underlying memory once all of the smart pointer variables have released their shared-ownership of it.
      • Ownership Cycle
        The std::weak_ptr<> type is for situations where you need access to the shared memory payload that is being collectively managed by several std::shared_ptr<>, but you don't want this std::weak_ptr<> to affect the reference count. A cyclic data structure is one example where that is needed.
      • Owning an Array
        A smart pointer can own an array (e.g., std::unique_ptr<int[]> piUnique(new int[32]);). When the time comes, it will use the correct destructor (delete[]).
      • Pointer to Base-Class
        The pointer-type managed by a smart pointer can be a base class of the actual memory allocation (i.e., std::unique_ptr<CRBase> poUnique(new CRDerived);).
      • Returning this
        Traditional C++ code might sometimes have wanted to do return this; (returning a CRType* raw pointer). To instead return std::shared_ptr<CRType>, derive your class from std::enable_shared_from_type<CRType> (so that an internal std::shared_ptr<> will start owning this object), and return shared_from_this() instead of this.
      • Smart Compiler-Temporary
        C++ creates a compiler-temporary for the new CRComplex(11,22) portion of std::unique_ptr<CRComplex> piVar(new CRComplex(11,22));. To guard against memory leaks from exceptions occuring before that compiler-temporary is given to the smart pointer, use std::make_unique<CRComplex>(11,22) in place of new CRComplex(11,22) (a std::make_unique<>() compiler-temporary is already a smart pointer as soon as it exists).
      • Custom Destruction
        Normally you rely on a smart pointer to decide both "when" and "how" to deallocate. But it is possible for you to provide a custom callable-object for the "how" part.
      • Template-Parameter
        You can access a smart pointer's template-parameter by std::unique_ptr<int>::element_type.
  • Expressions
    • Precedence
      C++ follows the standard precedence rule of mathematics, where 2 + 3 * 4 is parsed the same as if it had been written 2 + (3 * 4). Other precedence rules govern all aspects of expression evaluation.
    • Chain of Assignment
      The "compound assignment" iVar += 2; is an abbreviation for iVar = iVar + 2;. Programmers usually think about += only in terms of its effect (changing the value stored in iVar's memory), but that expression does also evaluate to a value. In fact, it evaluates to a "lvalue" which means that iVar+=2 is allowed to occur on the left-hand-side of some other assignment (although it would be weird to do so).
  • Program Flow
    C++ does have a goto statement, which alters the program flow by jumping directly to some labelled statement. But, usage of goto is strongly discouraged.
    • Loops
      • Basic Loops
        Loops can be written as while (condition) { body } or as do { body } while(condition);. The initialization and updating of a loop can be consolidated into for (iIndex = 0; iIndex < 10; iIndex++) { body }.
      • Looping over a Collection
        Any STL collection that provides iterators via begin() and end() (e.g., std::vector<int> veciVar = {0,11,22,33};) can be iterated by for (int iItem : veciVar) { body }.
    • The break Statement
      • A break in a Nested Loop
        When there is nesting, a break statement only terminates the innermost loop.
      • Fallthrough in a Switch
        A switch statement is an efficient substitute for many if (x==1) {body} else if (x==5) {body} else if (x=99) {body} chains. In a switch statement, each body is a sequence of statements preceded by a case 5: label. If you don't terminate that sequence of statements by break;, then program flow will fall-through and also perform the next sequence of statements. This is often a programming bug, so when you intentionally do this, write [[fallthrough]]; in place of break;.
    • Return Value
      It would be unsafe to return a reference to a local variable, but you can return a reference to a data member.
    • Parameters
      • Default Parameter
        When defining a function having a parameter, you can specify a default value that will be used if the caller doesn't provide a value for that parameter. For a member function, this should appear in the class definition as void DoMember(int aiParam = 123); (use ordinary syntax when implementing this function).
      • Unused Parameter
        Many compilers will warn you if you define a parameter that isn't used anywhere inside your function. If you are doing this intentionally, mark that parameter with [[maybe_unused]].
      • Variable Number of Parameters
        A parameter's type could be std::initializer_list<int>. This allows for passing either as DoFunction({11,22,33}) or as DoFunction(11,22,33).
    • Function Overloading
      • Discrimination Criteria
        "Overloaded" functions are multiple function implementations that all share a common name. They each have their own unique "signature" (the types of the parameters). When an overloaded function is called, the compiler is able to choose which implementation to use, based on the types of values passed to it. However, this overloading cannot discriminate between two implementations based only on return types.
      • Across Inheritance
        Instead of overloading each other, one function hides the other if it is in a derived-class.
    • Function-Pointer
      In traditional C++, a function could be passed-around using "function-pointer" variables. For example, this type alias specifies a pointer to a function that converts a float-pointer into an integer: typedef int (*PTPFTOI)(float*);.
    • Deprecating a Function
      An undesirable part of an API can be marked with [[deprecated]].
  • Objects
    An individual data member of an object is accessed by oObject.m_iMember. The dereference and member-access operators can be combined into poObject->m_iMember.
    • Accessibility
      In the class definition, you can precede a group of members by private: to disallow usage of that member from anywhere outside of that class' own member functions. Or, use protected: accessibility to also allow access to member functions of derived classes. The public: accessibility allows access from anywhere.
    • Unwanted Compiler-Generated Functions
      The compiler automatically generates several member functions, if the programmer hasn't manually programmed them (e.g., a default constructor). If you want to prevent the compiler from doing that, write that function's signature followed by = delete;.
    • Constructors
      • Ordering
        When an object is being instantiated from a class definition, you should usually initialize its data members. Immediately after reserving enough memory to store the object, C++ will automatically call a "constructor" function to perform that data-member-initialization. You implement your constructor as a member function having the same name as the class name, optionally with parameters, having no return type at all. When there's a hierarchy of classes, bass-class-constructors will be called before a derived-class-constructor.
      • Initialization by Copy-Constructor
        "Implicit initialization" (CRType oImp = oCopied;) and "explicit initialization" (CRType oExp(oCopied);) are equivalent in the sense that they both invoke CRType's "copy-constructor". The copy-constructor's signature is CRType::CRType(const CRType& aroCopied). This same copy-constructor will also be called whenever an object is passed-by-value into some function.
      • Initialization by One-Parameter-Constructor
        A one-parameter-constructor is essentially converting the type of some data (from that parameter's type, into this class type). To prevent client code from unintentionally doing this type-conversion when that client uses implicit-initialization syntax, mark this constructor definition with the explicit keyword.
      • Keeping Default Constructor
        The compiler provides a do-nothing implementation of the "default constructor" (one that has no parameters), if the programmer hasn't manually provided a custom implementation for it. But traditionally, if the programmer writes any other constructor (having some other overloaded signature of parameters), then this built-in default constructor was not provided by the compiler. If you do write some other constructor, but you still also want the compiler to provide its built-in default constructor, request it with CRType() = default;.
      • Delegation within Class
        With a modern compiler, a constructor could now delegate its implementation to some other constructor of the same class.
      • Memory Full
        The C language's malloc() returned NULL if memory was full when that allocation was attempted. C++'s CRType* poObj = new CRType; instead throws a std::bad_alloc exception. Alternatively, you could use std::set_new_handler() to specify a callback that will be repeatedly called until enough memory is available.
      • Placement New
        C++'s new operator allocates memory, and then executes some constructor within that memory. Low-level software might already have some memory pre-allocated for a new object, only wanting to run its constructor. To do so, call a "placement new" as CRType* poVar = new (preallocation) CRType;.
    • Destructors
      If a class constructor's implementation has any side-effect (e.g., allocation of memory, or opening of a file), then you usually desire the opposite side-effect to happen whenever that class' instances are going to get destroyed. The compiler knows when it's going to be destroying an object, but the programmer needs to specify that side-effect behavior within a "destructor" function which is written as CRType::~CRType() {body}.
    • Const Members
      • Const Data Members
        For data members that are constants or references, you must initialize them in a constructor's "initializer-list", which is written as CRType(int x) : m_iMember(x) {body}.
      • Const Member Functions
        A member function automatically has access to a this pointer variable whose type is CRType* and which is automatically pointing at the CRType object being manipulated by this member function. But you could have defined the member function as void CRType::DoAction() const {body}, in which case the type of this becomes const CRType* (that function's body can view data members but not alter them). This restriction can be ignored for individual data members (on a case-by-case basis) but marking the data member with mutable.
    • Static Members
      • Static Data Members
        A "static-data-member" is a data member marked by the static keyword. It describes one value that is shared by all objects of that class. Generally, a static-data-member must be initialized outside the class definition (by writing int CRType::m_giVar = 11; at the top of some implementation file).
      • Static Member Functions
        Most member functions conceptually manipulate one specific object, but a member function marked by the static keyword is for computation that is collectively about all objects of that class. The compiler doesn't provide any this pointer to a static-member-function, so a static-member-function can only access static-data-members. Whereas an ordinary member function is invoked on a specific object (oObject.DoAction()), any code may invoke a static-member-function without having any object (simply calling CRType::DoAction()).
    • Inheritance
      • Polymorphism Syntax
        A "derived" class "inherits" all members from its "base" class. A derived class is defined as class CRDerived : public CRBase {members};. Frequently, an object is allocated by instantiating the derived class, but "pointed-at" by a pointer variable whose type is "pointer-to-base-class": CRBase* poBase = (CRBase*)(new CRDerived);. If CRBase and CRDerived have provided two different implementations of the same DoAction() member function, then poBase->DoAction() would call the base class' implementation. But if the member function had been marked by the virtual keyword, then poBase->DoAction() would instead "polymorphically" call the derived class' implementation.
      • Qualification Syntax
        An overriding implementation of DoAction() can non-polymorphically refer to a base class' implementation as __super::DoAction().
      • Restricted Overriding
        A library author can use the final keyword to prevent an application programmer from further overriding a virtual-function. An application programmer can use the override keyword to require that the virtual-function he's implementing must have also existed within the base class.
      • Multiple Inheritance
        A C++ class may inherit from multiple base classes. However, that practice is often discouraged unless all but one of the base classes are "interfaces" (in C++, an "interface" takes the form of an "abstract class" having "pure" virtual functions that define an API without providing their own implementations).
    • Operator Overloading
      Programming custom implementations for operators is the mechanism for allowing a custom C++ class to be used as a "value type" (which then can behave similar to built-in primitive types like int). For example, client code expects to be able to do int iX = iY + iZ + 9;; if you want your custom class to also behave that way, then define its const CRType CRType::operator+(const CRType& aroRhs) {body}.
    • Nested Classes
      An inner class definition may be nested within some outer class definition. The member within that nested class is called CROuter::CRInner::m_iVar (those qualifications are unnecessary for code within that class' member functions).
    • Pointer to Members
      It is very common to use a type which is "pointer to int" (the type's name is int*). Must less common (but still possible) is using a type which is "pointer to int data member of class CRType" (the type's name is int CRType::*).
    • Low-Level C Structures
      • Union
        A struct CRType {...}; is the same as class CRType {...}; except for default accessibility. A union CRType {...}; is also the same, except all members in the union overlap in memory (space in memory is made for the largest member). Only one member can have meaningful data at one time.
      • Flexible-Array-Member
        A "flexible-array-member" is a data member which is an array having unspecified size. A flexible-array-member must be the last member, and there must be at least one normal data member before it.
      • Bitfield
        A "bitfield" structure member integral_type m_iVar : bit_count; is useful for alignment.
  • Templates
    • Templated-Functions
      A templated-function looks like any ordinary function, but with "template-parameter" placeholders for some types: template<typename T> T ComputeMin(T x, T y) { return (x < y) ? x : y; }. No compilation happens here where this templated-function is being defined; instead, this ComputeMin<>() will get compiled (with "specialization" T=int applied) during the first occurrence of that usage, as in int iResult = ComputeMin<int>(3,4);. In this case, you could have simply written int iResult = ComputeMin(3,4);, since the compiler could infer T=int from those parameter types.
    • Templated-Classes
      If a class is written as a template, then all of its members share that common abstraction, and are collectively specialized together.
    • Templated-Variables
      Modern C++ can also use template syntax with any global variable (defined outside all functions). Each template-specialization will behave like a separate variable.
    • Template-Parameters
      • Default Template-Parameters
        A template-parameter may have a default. That default may be defined in terms of a previous template-parameter: template<typename T0, typename T1 = CRA<T0> > class CRB {...};.
      • Deducing Template-Parameters
        Traditionally, template object initialization needed to explicitly list types: std::pair<float,int> fiVar = std::pair<float,int>(1.1F,2);. Modern C++ compilers can automatically deduce those types: std::pair fiVar = std::pair(1.1F,2);.
      • Variadic Templates
        A "variadic template" has been programmed to specialize a variable number of template-parameters: template<typename T0, typename... TN>.
    • Traits
      • Unary Predicate Traits
        A "predicate trait" is a boolean value based on some characteristic of a type. C++ provides many built-in "dummy templates" for a variety of these characteristics (e.g., std::is_floating_point<int>::value resolves to false at compile-time).
      • Binary Predicate Traits
        Predicate trait std::is_same<int,__int32>::value tests those two types for equality (__int32 is a platform-specific type on Win32 that is the same as int).
      • Array Traits
        At compile-time, std::rank<int[][8][8]>::value resolves to 3, and std::extent<int[3][4], 1>::value resolves to 4.
      • Unary Transform Traits
        A "transform trait" specifies a type that is similar to some other type, but with some added (or removed) characteristics. In this example, variable m_iMember will be a int data member for both specializations T=int and T=int&:
        template<typename T> class CRClass { std::remove_reference<T>::type m_iMember; };.
      • Binary Transform Traits
        Transform trait std::common_type<TLHS,TRHS>::type provides the coercion-result of a mixed-type-operation.
      • S.F.I.N.A.E. Conditional Compilation
        Modern C++ compilers follow the "Substitution Failure is not an Error" (SFINAE) principle, whereby the compiler silently eliminates a failed specialization from consideration, without that generating a compilation-error.
    • Metaprogramming
      • Numeric Template-Parameters
        A template-parameter usually serves as a placeholder for a type. But, a template-parameter could be numeric. This gives templates a capability of performing compile-time calculations by "template-metaprograms".
      • Collection of Types
        Some template-metafunctions may compute a type (or even a collection of types) as their template-metareturn. For these template-metafunctions, the established convention is to name that result type (a nested type alias).
    • Concepts
      Traditionally, a templated-function like template<typename T> T Add(T x, T y) { return x + y; } will allow substitution using any type (even types like T=std::string that actually shouldn't be "added together"). You can define a "concept", which is a logical criteria that only some types might satisfy: template<typename T> concept IS_ADDABLE = std::is_arithmetic<T>::value;. Then we can limit allowed substitutions: template<typename T> requires IS_ADDABLE<T> T Add(T x, T y) {...}.
  • Error Handling
    • Returning an Error Code
      When a low-level feature of the operating system needs to report an error, it usually does so by returning an "error code". The Win32 platform has four built-in enumerations of error codes, which the C++ language has combined into a single std::error_code type.
    • Assertions
      The assert(condition); statement is conditionally-compiled. When building a debug build-configuration, define the _DEBUG preprocessor symbol so that assert() will evaluate its condition (aborting the software execution if that condition evaluates to false). When building a release build-configuration, instead define the NDEBUG preprocessor symbol so that assert() efficiently generates no machine language code.
    • C++ Exception Handling
      If some exceptionally problematic situation might occur in code (either your code or library functions that you call), enclose that code within try {your code} catch (...) {handler}. When your problem occurs, your code "throws an exception" by doing throw 99;, and program flow will jump to the handler (even if that requires the C++ langauge to "unwind" a series of called functions). That data (99 in this example) is an explanation for what went wrong; it is typed so you could actually program a sequence of catch (int aiExplanation) {handler} with catch (...) meaning "all other types".
  • Standard Libraries
    The "Standard Libraries" are an OS-independent set of C++ classes that should be available in any C++ development environment. These classes are within a std namespace.
    • C-Runtime
      • Math
        • Basic Math
          The "C-Runtime" is a library that has been carried over from the C language. Its support for basic mathematics includes functions like std::max(), std::abs(), std::round(), and std::sqrt().
        • Trigonometry
          The C-Runtime includes functions like std::atan2() and std::exp().
        • Rational Numbers
          Just as 3 is a literal form of an integer, std::ratio<-4,3> is a literal form of a rational number.
        • Complex Numbers
          Standard C++ defines a complex number as std::complex<float> ffVar(3.0F, 4.0F);. Compute its "phase" using std::arg(), compute its "magnitude" using std::abs(), and compute its "complex conjugate" using std::conj().
        • Miscellaneous Math
          The C-Runtime also provides a variety of miscellaneous math functions, such as std::fma(2.0F, 3.0F, 4.0F); for computing 2*3+4.
      • Heap Memory
        • Allocation
          Dynamic memory management with the C-Runtime is done by calling malloc(), which returns a pointer to the newly-allocated memory. Give that pointer to free() when you are ready to deallocate that memory.
        • Memory Manipulation
          A whole block of memory can be set by one memset() function call. The memcpy() function copies a block of memory from one location to another. Two memory blocks can be lexicographically-compared by memcmp(). A memory block can be searched by memchr().
        • Leaks
          Memory "leaks" if you forget to deallocate something that you had dynamically allocated.
      • Time
        • Time as a String
          To specify a date/time, set various data members of a std::tm object. Calling std::asctime() converts that information into a string of exactly 26 characters. The computer's current time is obtained by std::time() as a std::time_t scalar value; std::localtime() converts that scalar value into std::tm.
        • Precise Time
          The current time obtained by std::time() is appropriate for uses like the timestamp of a file. But for a more precise measurement of time, set a std::chrono::time_point<std::chrono::high_resolution_clock> object to the value returned by std::chrono::high_resolution_clock::now().
        • Timespan as CPU Clockticks
          The C-Runtime keeps track of the amount of time elapsed since the library started running within your program. Query that value by calling std::clock().
      • Basic Algorithms
        • Search
          The C-Runtime's basic implementation of linear search by _lfind() will return NULL if the key does not already exist. Binary search by bsearch() expects the input to be both sorted and unique.
        • Quick Sort
          The C-Runtime provides a built-in qsort() implementation of the Quick Sort algorithm.
      • Executing a Shell Command
        A "system call" is the execution of a shell command, as in calling system("echo MESSAGE").
    • STL: Standard Template Library
      • Collection Classes
        • Sequence Collections
          • Vector
            • Construction
              A STL "vector" is a dynamically-resizing array-of-values. There are many ways to construct a vector, such as initializing it from a literal value using brace-syntax: std::vector<int> veciVar = {0, 11, 22, 33};.
            • Size
              STL implements a vector as having an allocation "capacity" (queried by veciVar.capacity()) that may be somewhat larger than that collection's current size (queried by veciVar.size()).
            • Equality
              Operators == and != compare two vector objects. If you want to compare a vector to some other kind of collection, then you need to use the more general std::equal() function.
            • Indexing
              Both the C-array-operator (veciVar[3]) and veciVar.at(3) allow random access to a vector's items. They differ in how they handle the error of a non-existent item. Because either of them provides a reference to that item, indexing can be used for getting it and for setting it. A reference is also returned by the special names for the first and last items veciVar.front() and veciVar.back().
            • Removing Items
              The erase() function removes an iterator-specified item from the vector, but doesn't do anything to that removed item. So if the item-type was a pointer, then you would still be responsible for any manual destruction of the actual object. To remove all items, use clear(). To remove the last item, use pop_back().
            • Inserting Items
              An inserted item does not need to have the exact same item-type as the vector, as long as it can be coerced into the vector's item-type. If there will be coercion, then insertion by emplace() will be faster than insert() because emplace() directly uses a single-parameter-cast-constructor instead of copy-constructing from a compiler-temporary that holds the coerced-value. To insert as the vector's last item, use emplace_back() or push_back().
            • Assignment
              One vector can be assigned from another vector, using the = operator.
          • Vector of Bool
            A special specialization std::vector<bool> compresses each item to a single bit. For this special specialization, the indexing operator doesn't return bool&. Instead, it returns a reference to an auxillary class that defines both vecyVar[iIndex].flip() and a cast-operator for getting and setting its bool value.
          • STL-Array
            A "STL-array" (an instance of class std::array<>) has a fixed size that is part of the templated type.
          • Deque
            A "deque" (an instance of class std::deque<>) has an array-of-values that can dynamically-resize in either direction. Unlike a vector, a deque's internal implementation does not store items in contiguous memory (so there is no "capacity"). Because a deque can grow in either direction, you can call push_front() in addition to push_back().
          • List
            A "list" (an instance of class std::list<>) is a doubly-linked-list. A list doesn't provide any random-access indexing. In order to access a particular item, you need to iterate to it.
          • Forward-List
            A "forward-list" (an instance of class std::forward_list<>) is a forward-link-only version of a STL list. Its iteration can only use ++ (not --).
        • Sorted Collections
          • A Pair as one Item
            A "pair" is an object whose type encapsulates two other types. A pair has a literal form, using braces: std::pair<char,int> ciVar = {'a',11};. Access each component as ciVar.first or ciVar.second. Pair assignment (= operator) copies-by-value the two elements from source to destination. Usually, assignment's destination is another pair. However, pair-assignment could also use std::tie() to decompose into two scalar variables.
          • Tuple
            A "tuple" is like a pair, except that a tuple can have more than two elements: std::tuple<char,int,float> cifVar('a', 11, 1.1F);. Access each component as std::get<0>(cifVar) or std::get<1>(cifVar) or std::get<2>(cifVar).
          • Map
            • Construction
              A "map" is a lookup-dictionary whose items are key/value pairs. Initializing a map from a literal value uses nested-brace-syntax:
              std::map<char,int> mapciVar
                = {{'a',0}, {'e',11}, {'i',22}, {'o',33}};
            • Sort-Class
              The ordering of items does not matter in a map-literal. What does matter is the "sort-class" template-parameter (the default is std::less<> if omitted). The sort-class determines the actual item-ordering within STL's internal representation (by ordering their keys). When iterators use ++ and --, they follow that sort-class order (not the map-literal's apparent order).
            • Custom Sort-Class
              You could define your own custom sort-class.
            • Size
              The size() function reports how many items are in the map. The count('i') function reports how many items are in the map having that particular key (0 or 1 of them).
            • Equality
              Map-equality depends on the order of items in their internal-representations (due to sort-class), not on the order of items in any literal-specficiation.
            • Indexing
              A map can be indexed-by-key (a lookup operation). Use either the C-array-operator, the at() function, or the find() function. These differ in how they deal with a non-existent key.
            • Removing Items
              The erase() function removes an item from a map. You can give erase() either an iterator, or the key of the item that you want removed.
            • Inserting Items
              The distinction between a map's insert() and emplace functions is the same as it was for vectors. With a map, you could also use C-array-indexing syntax, which will insert a new item if the map didn't already contain one with that key.
            • Assignment
              Assignment using the = operator will remove all existing elements.
          • Multimap
            A "multimap" (an instance of class std::multimap<>) is the same as a map, except that multiple items are allowed to have the same key.
          • Set
            A "set" (an instance of class std::set<>) is the same as a map, except that each item contains only the key (there is no value).
          • Multiset
            A "multiset" (an instance of class set::multiset<>) is the same as a set, except that the same item can occur multiple times.
          • Hash-Table
            The standard sorted collections each have an alternative implementation that is based on a hash-table. For example, instead of using std::map<char,int>, you might choose to use std::unordered_map<char,int,std::hash<char>>.
      • Iterators
        • Basic Iterators
          Various kinds of "iterators" collectively act as a common interface (designed to resemble the API of a C pointer) between multiple kinds of collection classes and multiple algorithms:
          void OutputVector(std::vector<int>& x) {
            std::vector<int>::iterator piIter;
            for (piIter = x.begin(); piIter != x.end(); ++piIter) {
              int iVar = *piIter;
              std::cout << iVar;
            }
          }
        • Ranges
          Often, a client uses parameters to specify a range of items to a STL algorithm. The convention is that the range's beginning is always inclusive, and its ending is always exclusive. To specify a range that involves all items, program as find(veciVar.begin(), veciVar.end(), 11);
        • Insert-Iterators
          Algorithm std::copy() has already been written by STL to call operators = and ++ on both source and destination iterators (overwriting destination items with data copied from source items). As such, that algorithm assumes the destination collection must already have at least as many items as the source collection. A std::back_insert_iterator<> "insert-iterator" is intermediary code which allows that same implementation of std::copy() to be used with an empty destination collection. That insert-iterator achieves this by re-defining the effect of operators = and ++. Operator = now has the effect of veciDesti.push_back(), and operator ++ now does nothing.
        • Reverse-Iterators
          Similar to an insert-iterator, a "reverse-iterator" allows you to use STL's existing std::copy() algorithm, but changes the effect that it has because of intermediary code that re-defines the iterator's operators. In this case, the result is a "reverse-copy effect".
        • Stream-Iterators
          Like insert-iterators and reverse-iterators, using a "stream-iterator" changes the effect of STL's existing algorithms. In this case, the iterator's = operator gets re-defined to have the effect of std::cout << source << " ";.
        • Custom Iterators
          It is possible to define your own custom iterator, by deriving from std::iterator<>.
      • Adapters
        • Stack
          A "STL adapter" exposes a theoretical data structure, with its storage being internally implemented by the usage of some existing collection class. By default, std::stack<int> uses a STL vector as its underlying collection class. This "stack" supports theoretical operations push(123), pop(), and peeking (named top()).
        • Queue
          By default, std::queue<int> uses a STL deque as its underlying collection class. This "queue" supports theoretical operations push(123) (essentially just a re-naming of push_back()) and pop() (essentially just a re-naming of pop_front()).
        • Binary-Heap
          A "binary-heap" data structure maintains the theoretical "heap-property" in a "shaped-tree" whose maximum-item is always at the tree's root. This std::priority_queue<int> binary-heap is useful if you need to be able to frequently identify and remove the largest item in a collection, without that collection having been linearly-sorted.
      • Callable-Objects
        The purpose of a "callable-object" is to abstractly refer to one specific invoke-able subroutine. A callable-object exists as an instance of std::function<>.
        • A Function Name as a Value
          The most simple form of a callable-object is the name of some existing function. If your software has already defined int DoAddition(int x, int y) {return x+y;}, then you might choose to define a callable-object as std::function<int(int,int)> DoMath = DoAddition;. In this case, the callable-object (DoMath) is a variable that can be manipulated like any other C++ variable. But the special thing about a callable-object is that it can be "invoked", either by a function-call-operator DoMath(11,22) or by std::invoke(DoMath, 11, 22).
        • Member Function
          A callable-object could also be assigned to a C++ member function:
          std::function<int(const CRType*,int,int)> DoMath 
            = &CRType::DoAddition;
          Now, invocation requires both parameters and a this pointer (an address of some pre-existing target-object): DoMath(&oTargetObject, 11, 22).
        • Implicit Getter-Function
          For every data member in a class, there is an implicit getter-function (even if the programmer didn't explicitly define that as a member function):
          std::function<int(const CRType*)> GetMember
            = &CRType::m_iMember;
        • Functor
          A "functor" is any object defined to have a custom overload implementation of the function-call-operator (operator()). A functor object can be assigned to any callable-object whose signature matches.
        • Lambda
          Just like an int has a literal form (e.g., 123), a callable-object has a literal form called a "lambda". In this example, the literal is being assigned to a callable-object variable:
          std::function<int(int,int)> DoMath;
          DoMath = [](int x, int y) {
              return x + y;
            };
        • Generic Lambda
          A lambda can be written in a generic way, by using auto for its parameter type.
        • Binding a new Function
          One callable-object can be transformed into another (unnamed) callable-object by "binding" it using std::bind().
      • Algorithms
        • Usage with a C-Array
          A C-pointer implements operations (e.g., ++) that some STL algorithms expect from their iterators. Thus, some algorithms (including std::copy()) allow you to use a C-array as if it were a STL collection.
        • Querying
          • Counting
            A sorted collection has a built-in mapciVar.count('i') member function that is most efficient. But for a sequence collection, you need to use the count() algorithm.
          • Searching
            The algorithm std::find_if(x.begin(), x.end(), callableObject) returns an iterator to the first item that it finds satisfying the callable-object (which must return a bool).
          • Accumulating
            The std::accumulate() algorithm iterates over items, accumulating them into a summation (or into some other kind of combination, if you provide a custom callable-object for doing each accumulation step).
        • Applying Item-by-Item
          • Processing Item-by-Item
            If you have a function like void DoubleEach(int& x) { x *= 2; }, then you can repeatedly apply that same callable-object, once per collection item. The algorithm to do so is std::for_each(v.begin(), v.end(), DoubleEach).
          • Ignoring Prior Values
            Unlike the DoubleEach() example, this function ignores existing item-values: int Compute99() { return 99; }. The algorithm to use it is std::generate(v.begin(), v.end(), Compute99).
          • Item-by-Item Combination of Two Collections
            The std::transform() algorithm combines two collections, item-by-item.
        • Modifying
          • Copying
            If you have an appropriate callable-object, then copying can be conditional, with item-by-item testing of that condition: std::copy_if(src.begin(),src.end(),desti.begin(),IsOdd).
          • Removing
            The std::remove() algorithm doesn't actually remove any items. Instead, it shifts the items-to-be-removed to the end of the collection. You need to call the collection class' item-removal function in order to complete the removal process.
          • Reordering
            The std::sort() algorithm uses a predicate callable-object for all of its internal comparisons between two items. That predicate defaults to std::less<>().
    • Strings
      • Unicode
        An 8-bit "Ascii character" has type char (its literal values are char cVar = 'h'; and "hello", its Standard Library functions are named like strlen(), and its Win32 functions are named like SetWindowTextA()). A "wide-Unicode character" has type wchar_t (its literal values are wchar_t cwVar = L'h'; and L"hello", its Standard Library functions are named liked wcslen(), and its Win32 functions are named like SetWindowTextW()). Windows programmers use preprocessor macros in TCHAR cxVar = _TEXT('h'); to alias either char cxVar = 'h'; or wchar_t cxVar = L'h'; (these macros are controlled by Windows' build system). That same switching mechanism also controls the expansion of macros _tcslen() and SetWindowText().
      • Avoiding Buffer-Overrun
        Some of the original C-Runtime functions were succeptible to a buffer-overrun-hack. For example, strnlen(NULL, 16) would cause a runtime-error. A safer alternative is available in Win32; strnlen_s(NULL, 16) simply returns 0 without trying to overrun the buffer.
      • Character Categories
        There are functions for classifying a character as belonging to a category. The examples isdigit('0'), isspace(' '), and ispunct('!') all return true (in the form of a non-zero int value).
      • C-String Manipulation
        A traditional C "string" is simply an array of char, where the end of the string data is terminated by a '\0' character. That terminating character is relied upon (but not counted) when strlen("abcde") measures that string's length to be 5. The lexicographical ordering of two C-strings is compared by strcmp(). To copy C-string data to some other char-array buffer, use strcpy() (or strcat(), which will concatenate by starting at the end of existing data).
      • String Class
        • Construction
          An object-oriented approach to storing string data is C++'s std::string object, which can dynaically-grow because it implements many of the same functions as an STL vector. Instead of using '\0' to terminate string data, you can query its strVar.size() function. A std::string object can be treated as a value (e.g., passing it into some function as a parameter value), because copy-construction has been provided. Initializing a string as std::string strVar("abc"); is equivalent to std::string strVar = {'a', 'b', 'c'};.
        • Equality
          The == operator exists for comparing the data inside two std::string objects.
        • Indexing
          Like any STL collection, you could iterate through string data using std::string::iterator. A std::string object uses contiguous memory for storing its characters. Application code is allowed to directly access the un-terminated character data (either via char* pcVar = &strVar[0]; or char* pcVar = strVar.data();) as long as it stays within the string's allocation area, and finishes its work before the next std::string operation. More conveniently, strVar.c_str() is like strVar.data() except that c_str() temporarily adds a '\0' at the end of the data-characters (making the buffer temporarily compatible with C-Runtime functions).
        • Inserting Characters
          Strings have an append() function (or the + operator) for appending a whole string at a time.
        • Algorithms
          A family of find() query algorithms return basic_string::npos (which is -1) to indicate "pattern is not found".
      • Casting Numeric Values
        • Number to String
          Formatted-printed by sprintf(pcBuffer, "%d", 11); is an easy way to cast from a number into a string. The more modern std::format("1+2={} and 4+5={}", 3, 9) outputs its formatted-string as a std::string object.
        • String to Number
          The inverse of formatted-printing can cast from a C-string into a numeric variable: sscanf("11", "&d", &iVar);.
      • Aliased Strings
        The newer std::string_view class is like std::string, except that its copy-construction does aliasing instead of copying character data.
      • Regular Expressions
        The std::regex_match() function compares an input string against the regular expression pattern stored in a std::regex object. The result of that matching is output into a std::cmatch object (which can be converted into a std::string by str()).
      • Locale
        The behavior of some string functions will depend on the computer system's "locale" setting. The Win32 implementation of some functions (e.g., atoi()) have a locale-sensitive alternative (atoi_l()).
    • IOStreams
      • Stringstream
        A "stream" uses operator << to stream from general-purpose-data into a character sequence. A stream uses operator >> to stream the other direction. One possible place where the character sequence could go is into a std::stringstream object, which captures the character sequence as a C++ variable. That variable can then be accessed as a read-only string. Alternatively, a character sequence can be output to the Console by streaming to std::cout. Alternatively, a character sequence can be streamed out to a file in the filesystem.
      • Streaming Custom Types
        To enable std::cout << oVar; where oVar has a custom type, you would need to implement an overload of the stream-operator operator<<().
    • Filesystem Access
      • Moving and Deleting
        An existing directory or file can be moved by using rename() to change its name. An existing directory or file can be deleted by remove().
      • Directories
        Concatenating drive, directory, filename, and extension strings into one long path string used to be done by _makepath(), but now the std::filesystem::path can do so more elegantly (using its / operator). Some of the most common OS shell commands have C++ versions, such as _chdir(), _mkdir(), and _rmdir().
      • Binary-Files
        A binary-standard-file is opened by FILE* poFile = fopen("pathandfile", "w+b"); and closed by fclose(pofile);. The file has a "seek-pointer", which is the position (measured from the file's beginning) where the next fwrite() or fread() operation will occur. That seek-pointer could be queried by ftell() or moved by fseek().
      • Formatted I/O
        A standard-file can also use the formatted-operations fprintf() and fscanf().
      • Newline-Converted-Files
        If you know that a file's data is to be interpreted as characters, then you could choose to have special operating-system-conversion of every newline character. To use that kind of "newline-converted-file", open it with "w+" instead of "w+b".
      • Size of a File
        There are two ways of measuring a file's size. Either call _filelength(), or access the st_size data member of an object returned by _fstat().
      • Redirecting stdout
        The _dup() function is useful for redirecting stdout to a file written on the disk.
      • Pipes
        A "pipe" is like a name-less file, used for interprocess communication.
      • Console I/O
        The _kbhit() function pauses until console-user presses a key. Then, _inp() reads a byte from the console.
      • Streamed I/O
        An IOStreams stream can target a file.
    • Multithreading
      • Thread Creation
        To create a thread in modern C++, instantiate std::thread, passing it a callable-object for the worker thread's entry point. To block yourself for an arbitrary amount of time, call std::this_thread::sleep_for(). To block yourself until the worker thread has finished its work, call oThread.join().
      • Critical Section
        A "critical section" is a code fragment that should be treated as atomic because of how it uses shared variables or resources. Extra code (inserted before and after a critical section) should enforce the mutually exclusive execution property (a.k.a. "mutex" property) within this critical section without causing "deadlock" nor "starvation".
      • Atomic Values
        If the only content of a critical section is the manipulation of one variable, then the easiest synchronization approach would be to use std::atomic<> and its associated arithmetic functions.
      • Locks
        The mutually exclusive execution property of a critical section is provided by a "lock" synchronization primitive (type std::mutex with functions lock() and unlock()). The lock maintains a record of which thread called lock(), and only that "owner-thread" is allowed to call unlock().
      • Events
        An "event" is a signal communicated from one thread to another thread.
      • Return Value
        To return a value from your worker thread, use a std::future<> return-value-getter object, whose get() function will block your client thread if the worker thread hasn't yet called set_value() on a corresponding std::promise<> return-value-setter object.
      • Semaphores
        • Resource Management
          Multiple threads might share a limited number of resources. Now, your code fragment isn't a critical section (multiple threads can now act simultaneously), but semaphore-based synchronization is needed to manage the resource pool using P() and V() functions that block a thread whenever all resources have been depleted.
        • Fragment Ordering
          A semaphore can be used to order the execution of code fragments in different threads. In this case, the "resource" isn't a physical thing, it's permission to execute the subsequent code fragment.
        • Ring Buffer
          In an architecture where one thread produces a stream of products while another thread consumes those products, putting a "ring buffer" between the threads will allow variation in the pace of production and consumption.
        • Readers and Writers
          Semaphores provide a solution to the classic "Readers and Writers Problem", where threads are either trying to read or write a file (multiple reads can occur concurrently, but writes require exclusive access).
      • Monitors and Channels
        A "monitor" is a class that combines/encapsulates shared variables, mutually-exclusive access to those variables, and any other synchronization needed.
    • RNG: Random Number Generator
      The RNG from the Standard Libraries (std::random_device) is of better quality than the obsolete implementation in the original C-Runtime.
  • Obsolete Features
Advertisement