"To have the president-elect of the United States simply reject the fact-based narrative that the intelligence community puts together because it conflicts with his a priori assumptions — wow."

— General Michael Hayden, Director of NSA and CIA for George W. Bush

After Justice Scalia died, Senate Republicans ignored their constitutional obligation to confirm a replacement, saying "Give the people a voice". But when the people of North Carolina elected a Democratic governor, Republican legislators voted at the last minute to limit his power.

The GOP used to espouse ideals you could almost believe in. Now they're nothing but hypocrites and liars.

C++ Notes

Language notes — C++

C++ Notes

Compiled by Jeremy Kelly
www.anthemion.org

These are my personal C++ notes. They document C++03, plus some features from C++11-17, which is generically labeled modern C++. The notes are here for my convenience, but you are welcome to use them, subject to the terms of the Creative Commons Attribution-ShareAlike 4.0 International License .

The notes do not provide an exhaustive description of the language; concepts that seem obvious to me often go unmentioned. Conversely, not everything here is necessarily useful. The notes are not drawn from the C++ standard, but from various secondary sources. If you find a mistake, please let me know.

All example code follows the Split Notation.

This page includes a two-column print style. For best results, print in landscape, apply narrow margins, and change the Scale setting in your browser's print options to 60%.

Contents

Basic types

Characters

char represents one element from a single character set, usually (but not necessarily) ASCII. It is one byte in size.

wchar_t represents one element out of all character sets in all locales supported by the implementation. It is usually two bytes in size. Wide character literals are prefixed with L:


wchar_t o = L'A';

Character types are usually signed by default.

Escape sequences allow special characters to be represented within character and string literals:

Sequence Character
\a Bell
\b Backspace
\f Form feed
\n Newline
\r Carriage return
\t Tab
\v Vertical tab
\\ Backslash
\' Single quote
\" Double quote
\? Question mark

Arbitrary characters can be specified with a single backslash, followed by up to three octal digits:


char o = '\101';

or a backslash and an x, followed by any number of hex digits:


o = '\x0041';

Integers

Integers are signed by default:

Type Common size
short 2 bytes
int 4 bytes
long 4 bytes
long long 8 bytes

Integer literals can be prefixed to specify their number base, or suffixed to specify their size or signedness:

Modifier Type Meaning
0 Prefix Octal
0x Prefix Hex
U or u Suffix unsigned
L or l Suffix long
LL or ll Suffix long long

Enumerations

Enumeration elements are called enumerators. A given enumerator is normally equal to an int in size, but may be larger if its value is outside the range of that type. Visual Studio warns and then truncates explicit enumerator values outside this range.

An enumerator can be used anywhere an int is used, but an int cannot replace an enumerator without a cast.

Enumerations can be defined anonymously. They can also be defined with local scope:


void gOut() {
  enum {
    oReady = 10,
    oDone = 20
  };
  ...

Floats

Floats are always signed:

Type Common size Common precision Suffix
float 4 bytes 6 digits F or f
double 8 bytes 15 digits
long double 10 bytes 19 digits L or l

Visual Studio treats long double as double.

Float literals can be specified with scientific notation, the exponent being marked with E or e:


const double oTol = 1E-3;

When an integer and a float are passed to an operator, the integer is automatically converted to floating-point.

References

In general, an l-value is an object that can be referenced, with either a variable or with a pointer or object reference. An r-value is typically a temporary object.

Pointers

The type referenced by a pointer is the base type of that pointer. A void pointer can store the address of any object, but it cannot be dereferenced or participate in pointer arithmetic without first being cast.

In modern C++, a pointer can be set to reference no object by assigning nullptr. Unlike common implementations of NULL, this value is not implicitly converted to an integer.

Class variable pointers

A pointer can be made to reference a variable within a class type. Such a pointer does not reference a particular variable instance, as other variable pointers do. Instead, it references one member within any instance of the containing type. It is like an object-relative offset rather than an absolute memory address:


struct tEnv {
  tEnv(int aWth, int aHgt): Wth(aWth), Hgt(aHgt) {}

  int Wth;
  int Hgt;
};

int tEnv::* opSize;

The declaration is similar to that of an ordinary value pointer, but the indirection operator is prefixed by the class type. Since class variable pointers do not reference specific instances, no instance is needed to set such a pointer. Only the type is specified:


opSize = &tEnv::Hgt;

An instance is needed to dereference the pointer. The dereferenced pointer is applied to the instance, and read or written just as the variable would be:


tEnv oEnvSm(6, 4);
int oSize = oEnv.*opSize;

Function pointers

A function pointer can reference any function with a matching signature:


short gCt(int aID);

short (* odCt)(int) = &gCt;

The pointer is declared like a function, with the indirection operator and pointer name replacing the function name. Parameter names can be specified or not. When setting the pointer, the address of operator can be applied to the function, or it can be omitted:


odCt = gCt;

Once initialized, function pointers can be invoked directly:


short oCt = odCt(100);

or they can be explicitly dereferenced:


oCt = (*odCt)(100);

Where function pointers are concerned, static class functions and non-class functions are identical. The same pointers can reference functions of either type, if the signatures match:


struct tCat {
  static short sCt(int aID);
};

odCt = &tCat::sCt;

Non-static class function pointers

Pointers can also reference non-static class functions. Much like class variable pointers, these reference a specific class type as well as a function signature:


struct tIt {
  short WgtMin(int aCt) const;
};

short (tIt::* odWgt)(int) const = &tIt::WgtMin;

As with non-class function pointers, the indirection operator and pointer name replace the function name in the declaration. As with class variable pointers, the indirection operator is prefixed with the class type. Unlike ordinary function pointers, the address of operator is required when assigning to the pointer. The class is specified when setting the pointer, but no instance is needed.


odWgt = &tIt::WgtMin;

An instance is needed to dereference the pointer, however. The dereferenced pointer is applied to the instance and invoked as the class function itself would be:


tIt oIt;
short oWgt = (oIt.*odWgt)(10);

tIt* opIt = new tIt;
oWgt = (opIt->*odWgt)(20);

Arrays

Multidimensional arrays

Multidimensional arrays are implemented as arrays of arrays:


int oGrid[gXMax][gYMax];

Elements are accessed by dereferencing each array in turn:


int o = oGrid[oX][oY];

This is equivalent to:


int o = *(*(oGrid + oX) + oY);

The rightmost dimension is the only one providing a continuous linear mapping of memory to array values.

When declaring arrays as function parameters, the size of all dimensions but the first must be specified:


void gScan(int aGrid[][gYMax]);

This allows the offsets to be calculated when dereferencing the array.

Array initializer lists

Like other aggregates, arrays can be initialized with initializer lists:


int oOff[] = { 5, 10, oX + 10 };

List elements need not be constant. Such lists can be used only during initialization.

If the array size is not specified, it will be inferred from the initializer list. If it is specified, and if initializers are not provided for all elements, the remaining elements will be default-initialized.

When initializing multidimensional arrays, the innermost list levels correspond to the rightmost array dimensions:


int oGrid[2][3] = {
  { 11, 12, 13 },
  { 21, 22, 23 }
};

String literals

When initializing character arrays with string literals, the compiler adds and initializes one element for the zero-terminator, even when the string is empty:


char oText[] = "";

String literals separated by whitespace are joined into continuous arrays by the compiler:


char oText[] = "ABC" "DEF"
  "GHI";

All string literal data is allocated for the lifetime of the program, even when defined within a block.

Class types

Class types include classes, structures, and unions.

Namespaces, enumerations, typedefs, and other class types can be nested within class definitions, just like variables and functions. Such elements have class scope, plus the specified access level.

Class types can also be defined with local scope, as long as all functions are defined within the class definition:


void gExec() {
  class tNode {
  public:
    int Cd;

    void Send() { gSend(Cd); Cd = 0; }
  };
  ...

Forward declaration allows a type to be referenced before it is defined, so long as its members are not accessed, and its size not calculated:


struct tNode;

class tGraph {
public:
  void Add(const tNode& aNode);
  ...

Access levels

Class elements are private by default. Structure and union elements are public by default.

The friends of a class gain access to all class elements accessible to the class itself, including protected elements inherited from parents. Because private parent elements are inaccessible to the class, they are inaccessible to its friends.

The friends of a base class have access to the base class members of all derived classes, even when the derived classes do not. No other friend obligations are inherited by derived classes.

Class types and functions can be declared friends. Neither need be defined at the time of the friend declaration.

Class type friends

Classes, structures, and unions must be identified as such when declared as friends:


struct tBatch {
  friend class tList;
  friend struct tItr;
  friend union thIdx;
  ...

Friend privileges are not inherited by types derived from friend types.

Function friends

Function friends are declared with a complete function prototype:


class tSet {
  friend void gSort(tList& arList);
  friend void tStore::Write();
  ...

Class variables

const class variables

Non-static const class variables must be initialized with member initializers:


struct tLine {
  tLine(unsigned int aCode): Code(aCode) {}

  const unsigned int Code;
};

Static class variables

static class variables are not associated with specific instances of the containing type; instead, a single instance is shared by the entire class. These variables are initialized before main executes. Their relative initialization and deinitialization order is undefined.

Declaring a non-static class variable also defines the variable. By contrast, static class variable declarations do not serve as definitions, so the variables must be defined outside the class definition. Technically, even static const integer variables require explicit definition, even when these are initialized inline:


struct tEl {
  static const int sIdxMax = 255;
};

const int tEl::sIdxMax;

Visual Studio does not enforce this requirement.

Bit Fields

Bit fields allow the memory used by an integer type to be split between several class variables. The bit size of each variable is specified after the variable name. Omitting the variable name creates an anonymous field, which is typically used for padding. The implementation may also insert padding to preserve type boundaries:


struct tMsg {
  unsigned char X: 2;
  unsigned char Y: 2;
  char: 3;
  bool Set: 1;
};

The size assigned to a given field cannot exceed that of the field's type. Only integer types and enumerations can be used as fields.

It is impossible to obtain the address of a field. Fields can be used in any class type, and, like other variables, fields defined in unions share memory with other union variables.

Class functions

Static class functions

static class functions are not associated with a particular instance of the class, so this is not defined within them, and they cannot call other class functions unless they are also static, or unless the non-static functions are invoked through an instance.

const class functions

const functions cannot invoke non-const functions in the same instance, and they cannot modify variables in that instance unless those variables are declared mutable. They are allowed to modify static class data.

volatile class functions

When applied to a class function, the volatile qualifier indicates that the function is thread-safe. volatile class functions cannot invoke non-volatile functions on the same instance. They can modify non-volatile class data, however.

Constructors and destructors

A constructor is called when storage is allocated. If no constructor is accessible at a given point in the code, no instance can be allocated there, dynamically or otherwise.

Class variables are initialized by a constructor in the order of their declaration within the class, not their order within the member initializer list.

Class types that implement destructors cannot be automatically or statically allocated unless their destructors are accessible. They can be dynamically allocated in this situation, but not deleted.

Default constructors

A default constructor is one with no parameters, or one for which all parameters have defaults. The function call operator is not required when invoking a default constructor:


tEl oElTemp;
tEl* opEl = new tEl;

However, using the operator causes the instance to be zero-initialized in some cases, particularly when it is a POD type.

If no constructor is explicitly defined for some class type, the compiler will create a default constructor for it, as long as all the type's members can themselves be default-constructed. All constructors call the default constructors of member variables that are not explicitly constructed in their initializer lists.

The compiler does not implement destructors on its own, but it does allow destructors to be invoked on types that lack them. Such calls have no effect.

If an array is not completely initialized when it is defined, the base type of that array must provide an accessible default constructor, whether explicitly defined or otherwise.

Copy constructors

A copy constructor accepts a reference to an instance of the same type. Other parameters can be defined if default values are provided for them. When a class type variable is initialized with an instance of the same type, the copy constructor is called, not the assignment operator:


tIt oItTemp = oIt;

For this reason, when implementing a copy constructor, it is best to overload the assignment operator too.

If no copy constructor is explicitly defined, the compiler will create one that calls the copy constructor of each member, as long as each has an accessible copy constructor.

Construction and destruction with inheritance

When a derived class is instantiated, constructors in the least-derived base classes are invoked first. When it is destroyed, destructors in the most-derived classes are invoked first.

If a base class has a default constructor, it need not be explicitly initialized by the derived class. If it does not, a base class initializer must be specified in the initializer list:


struct tCtl {
  tCtl(int ahWin): hWin(ahWin) {}

  int hWin;
};

struct tBtn: public tCtl {
  tBtn(int ahWin): tCtl(ahWin) {}
};

There is no way for a derived class to initialize base class variables directly.

Declaring a base class destructor virtual makes destructor calls polymorphic. This ensures that derived destructors will be called even if the instance happens to be deleted through a base class pointer.

Inheritance

Name hiding

If a derived class defines a function with the same name and signature as one in a base class, and if the base class function is not virtual, the base function will be hidden by the derived function within the derived class. Base class variables are also hidden by derived class variables with the same name. Hidden base members can be accessed from derived classes by using the scope resolution operator, which prefixes the identifier with the name of the targeted class type, and two colons:


struct tCont {
  bool Avail;

  void Sort();
};

struct tList: public tCont {
  int Avail;

  void Sort() { tCont::Sort(); }
};

Scope resolution can also be performed from outside the class:


tList oList;
bool oAvail = oList.tCont::Avail;

Virtual functions

Declaring a class function virtual allows derived classes to override it, which replaces the implementation. By contrast, non-virtual functions can only be hidden by derived classes. Class types with at least one virtual function are said to be polymorphic.

Though it is never accessible from a derived class, a private base class function can be overridden, causing the derived implementation to be called when invoked by the base. The base implementation remains private.

Virtual functions cannot be inlined by the compiler.

Pure virtual functions

A class declaring one or more pure virtual or abstract functions cannot be instantiated. Neither can its descendents, unless all such functions have been overridden somewhere in the inheritance chain. In other respects, pure virtual functions are like other class functions. They can even be defined in the abstract class, and invoked:


struct tPerson {
  virtual void vRep() = 0;
};

void tPerson::vRep() {
  cout << "Person" << endl;
}

struct tCust: public tPerson {
  void vRep() {}
};

void gExec() {
  tCust o;
  o.tPerson::vRep();
}

In particular, pure virtual destructors, which are sometimes use to make classes abstract, must be defined if derived classes are to be instantiated.

Inheritance access levels

When a class type specifies a base class in its constructor, it can prefix the base with an inheritance access level that limits the accessibility of base class members outside the derived class. In particular, protected inheritance renders public base members protected in the derived class, private inheritance renders all base members private, and public inheritance leaves base members unchanged. public and protected base members remain accessible in the derived class and its friends:


class tFig {
public:
  int Wth;
};

class tSq: private tFig {
public:
  tSq() { Wth = 10; }
};

To prevent non-public base classes from being accessed indirectly, derived classes cannot be upcast to protected or private base classes without an explicit cast:


tFig* opFig = (tFig*)&oSq;

Multiple inheritance

By default, if a class derives from multiple base classes, and if two or more of the base classes share a common ancestor, multiple instances of that ancestor will be defined in the derived class. To avoid ambiguity, it is necessary to specify one of the intermediate classes when referencing a duplicated variable or function.

To prevent duplication, the common ancestor can be inherited as a virtual base class by prefixing it in the intermediate class constructors with virtual:


struct tVehi {
  const string Model;

  tSpanTime Age() const;
};

struct tBoat: virtual public tVehi {
};

struct tCar: virtual public tVehi {
};

struct tBoatCar: public tBoat, public tCar {
};

Unions

All members of a union share the same location in memory. When one member is assigned, the content of the other members becomes undefined.

A union cannot contain any variable that implements a constructor or a destructor, or that overloads the assignment operator. Although they are class types, unions cannot participate in inheritance, or declare virtual functions.

Unions defined within other class types can be anonymous:


struct tVal {
  union {
    int uX;
    int uY;
  };
};

Variables within these unions are accessible as though they were members of the containing type. Unions with file scope can be made anonymous, but they must be declared static:


static union {
  int uID;
  int uIDEx;
};

Anonymous unions cannot define functions or non-public variables.

Aggregate and POD types

Aggregate types include arrays, and any class type that:

  • Has no base class;
  • Has no user-defined constructor;
  • Contains no private or protected data, unless that data is static;
  • Has no virtual functions.

Aggregates can be initialized with initializer lists. The list values can include other classes or structures:


struct tOpt {
  tOpt(int aLenMax): LenMax(aLenMax) {}

  int LenMax;
};

struct tConn {
  int ID;
  string Name;
  tOpt Opt;
};

tConn oConn = { 100, "Test", tOpt(10) };

The values must be specified in the order of their declaration within the type. As with arrays, elements for which no value is provided will be default-initialized. Initializer lists cannot be used to assign, only to initialize.

A POD type is an aggregate that:

  • Has no member that is not itself a POD type or an array of POD type, unless that member is static;
  • Contains no references;
  • Has no user-defined assignment operator or destructor.

POD types can be serialized and deserialized with block memory transfers.

Type aliases

A type alias is an alternative name for a type. It can created as a typedef or as a using alias.

Typedefs

A typedef is defined by placing the typedef name where the variable or function name would be found, and then prefixing with the typedef keyword:


typedef vector<int> tSeq;

Thereafter, the typedef name can be used in place of the original type:


tSeq::size_type oIdx = 1;

The same syntax is used for more complex types. If a non-static class function pointer is declared this way:


short (tIt::* odWgt)(int) const = nullptr;

an equivalent typedef can be defined with:


typdef short (tIt::* tdWgt)(int) const;

Typedefs can have namespace, class, or local scope.

using aliases

In modern C++, type aliases can also be created with the using keyword. Instead of placing the alias name where the variable or function name would be found, it appears on the left side of an assignment. The notional variable or function name is simply removed:


using tSeq = vector<int>;

The same syntax applies to more complex declarations:


using tdWgt = short (tIt::*)(int) const;

Unlike typedefs, using aliases can be templatized:


template <typename zEl>
using tSeqsByCd = map<string, vector<zEl>>;

The resulting alias template is specialized like any other template:


tSeqsByCd<int> oSeqsByCd;

Like typedefs, using aliases can have namespace, class, or local scope, unless they are templatized, in which case they cannot be local.

Type conversion

Many basic types are automatically converted to other types, even when precision would be lost. Compilers generally warn when this is done. Some automatic conversions can be performed without losing precision. These are called type promotions.

A conversion from a user type to another type is implemented with a conversion operator, which returns an instance of the new type. A conversion from another type is implemented with a constructor that accepts the original type as a parameter.

Conversion operators

The conversion operator declaration is unlike other operators: no parameter is accepted, and no return type is specified, not even void. Instead, the conversion type is specified after the operator keyword, and it is followed by the function call operator:


struct tOrd {
  operator int() const { return 100; }
};

Conversion operators allow user-defined types to be implicitly converted to other types:


tOrd oOrd;
int oID = oOrd;

When defined, conversion operators are also invoked during explicit conversions:


cout << (int)oOrd;

Conversion by construction

By default, if a constructor can be invoked with a single parameter, it will be used by the compiler to perform implicit conversions from the parameter type to the constructed type:


struct tCust {
  tCust(int aID = 0, float aBal = 0.0);
};

tCust oCust = 1;

It is usually preferable to mark these constructors explicit. When this is done, the conversion can be performed only with a cast.

Typecasts

Upcasting converts a derived class reference to a base reference. Downcasting converts a base reference to a derived reference. Crosscasting converts a base class reference to another base reference, which is possible only when the instance derives from both.

dynamic_cast

dynamic_cast uses RTTI to convert pointers and references within a class family:


tCtl* opCtl = dynamic_cast<tCtl*>(opBtn);

tCtl& orCtl = dynamic_cast<tCtl&>(orBtn);

When dynamic_cast succeeds, the converted pointer or reference is returned. When a pointer cast fails, or when the source pointer is zero, zero is returned. When a reference cast fails, bad_cast is thrown. Because they provide no type information, void pointers cannot be passed to dynamic_cast.

A given upcast will succeed if the destination is a unique, accessible base class of the instance type. If the instance uses multiple inheritance, and if multiple matching base instances are found, the cast will fail. It will also fail if the destination is a private or protected base class.

A given downcast will succeed if the source pointer or reference type is polymorphic, if the destination type is accessible, and if the instance type matches or derives from the destination type.

A crosscast will succeed if the source pointer or reference type is polymorphic, if the destination type is accessible, and if the instance type derives from both the source and the destination types. The destination type need not be polymorphic.

Any cast will succeed if the destination type matches the instance type.

static_cast

static_cast does not use RTTI, never requires a polymorphic source type, and performs no run-time check, so it cannot return zero or throw. It is used to perform upcasts and downcasts. It also converts between 'related' types, like integers and floating-point types:


bool oVal = true;
int oIdx = static_cast<int>(oVal);

const_cast

const_cast is used to remove const or volatile qualifiers:


struct tCurs {
  int Idx;

  void Skip() const;
};

void tCurs::Skip() const {
  ++(const_cast<tCurs*>(this)->Idx);
}

This is safe only if the value to be cast was not originally declared with such qualifiers.

reinterpret_cast

reinterpret_cast converts 'unrelated' types. It generally produces a value with the same bit pattern as its argument. It converts between all pointer types, and between all pointer and integer types.

C-style casts

The traditional C-style cast can perform any conversion expressible with a combination of static_cast, reinterpret_cast, and const_cast.

Strict aliasing

Aliasing occurs when a memory location is referenced by two or more l-values, whether these are variables, pointers, or references. When this happens, the compiler must ensure that changes to one alias are reflected in the others. C and C++ enforce strict aliasing rules that limit the number of aliases. For an l-value to be valid in C++, one of the following must be true:

  • The l-value must have the same base type as the referenced object, after disregarding cv-qualifiers and signed/unsigned distinctions. This includes l-value types that are parents of the referenced instance type;
  • The l-value must be an aggregate or a union that includes one of the above types, whether directly, or within a recursively-contained aggregate or union;
  • The l-value must be a pointer or reference to char or unsigned char.

If none of these criteria are met, the alias produces undefined behavior.

Declarations

Identifiers can contain letters, numbers, and underscores, but they cannot begin with numbers. Many identifiers beginning with underscores are reserved for use by the environment.

A declaration provides basic information about an identifier to a translation unit, so that it can be referenced. Some declarations also define elements, allowing them to be used in other ways. An element can have multiple declarations, but it must have only one definition, or a linker error will result. Function declarations are also known as prototypes.

A variable declaration serves as a definition if storage is allocated, which happens unless:

  • The variable is class-static;
  • The variable is declared extern without being initialized.

A function declaration serves as a definition if the function body is inlined.

Initialization

Objects can be initialized with the assignment operator:


int oIdx = 1;

or with constructor syntax:


int oMax(12);

These techniques allow type narrowing, where an argument is automatically converted to a smaller compatible type, even if information would be lost. Most compilers warn when this is done.

In modern C++, an object can also be initialized by assigning with one or more arguments surrounded by curly braces:


int oMask = {0x0000FFFF};

In the past, this syntax was allowed only when initializing aggregates. If the object has a constructor that accepts an initializer_list, the braces and their content will be interpreted as an instance of that type, unless the braces are empty, in which case the default constructor will be invoked, if possible. To specify an empty list, place empty braces inside parentheses:


tQueueAct oQueue({});

or inside a second set of braces.

If there is no initializer_list constructor, the brace elements will be matched against the constructors that are defined, as though the traditional constructor syntax had been used:


tPt oCtr = {0.0, 0.0};

If the object has no constructors, the brace content will be used for memberwise initialization. The values will be assigned to the object's members in the order the members were defined within the class type. The braces can contain elements with different types.

Curly braces can also be used in place of parentheses, in something like the constructor syntax:


tPt oStart{0.0, 1.0};

This is sometimes called universal or uniform initialization. If a constructor can be matched with the arguments, it will be invoked, otherwise the brace content will be used for memberwise initialization.

In all cases, curly braces prevent narrowing. They can also be used to avoid vexing parse problems.

Scope

The scope of an identifier determines the parts of a translation unit from which the identifier is accessible.

Namespace scope

An identifier declared within a named namespace has namespace scope. It is usable anywhere after its declaration.

The global namespace is defined automatically. It is unnamed, and it contains all other namespaces. An identifier in this namespace has file scope, and it is also usable after its declaration.

Function and local scopes

Every label has function scope, making it usable throughout the function that defines it, even before it is declared. This allows goto to jump into or out of blocks. No other identifier has this scope.

If it is not a label, an identifier declared within a function has local scope. It is usable within the containing block, after the declaration.

Class scope

If it is outside of a function definition, an identifier declared within a class type has class scope. It also has a public, protected, or private access level, but this is not a scope per se. An identifier with class scope is usable anywhere its access level permits.

Storage classes

For the most part, a variable's storage class determines when and how it is allocated and deallocated. Some storage classes can also be applied to functions.

static

Local static variables are initialized the first time they are encountered. In this they differ from class-static variables, which are not initialized in a predictable order. In both cases, the variables are allocated on the heap, and a single instance is shared by all function invocations, or by the entire class. static variables persist for the lifetime of the program, and, in all cases, their relative deinitialization order is undefined.

extern

The extern storage class affects linkage, but it does not change the way a variable is stored. It does determine whether storage is allocated for variables. In most cases, storage is allocated automatically when a variable is declared. However, an extern variable declaration that does not initialize the variable does not allocate storage. This allows a single variable instance to be declared and referenced by multiple translation units.

Non-class functions have extern storage by default. Where functions are concerned, this only affects linkage.

auto

An auto variable is allocated on the stack when the program reaches its declaration, and it is deallocated when the program leaves the containing block. Local variables have auto storage by default.

register

register requests that the variable be stored in a register. This request may be ignored by the compiler.

mutable

mutable class variables can be modified by functions declared const. Unlike other storage classes, mutable does not affect linkage or variable lifetimes.

Linkage

Storage class and scope combine to define an element's linkage. This determines whether declarations that use the same identifier are understood to reference the same element, or different ones.

const file-scope variables always have internal linkage. For non-const variables, and for those without file scope, the linkage is:

File or
namespace
Local Class
(default) external none none
static internal none external
extern external
auto none
register none

The linkage for functions:

File or
namespace
Class
(default) external external
static internal external
extern external

Because external linkage is the default, there is never a need to declare functions extern. If a function is declared static and then extern in the same translation unit, it will continue to have internal linkage.

No linkage

Elements with no linkage cannot be referenced from outside the containing scope. Same-name identifiers therefore reference different entities when used in different scopes. Local variables have no linkage.

Internal linkage

Elements with internal linkage can be accessed from other scopes, but not from other translation units. Such identifiers therefore reference different entities when used in different translation units. static file-scope variables and functions have internal linkage.

External linkage

Elements with external linkage can be referenced from other scopes and other translation units. Such identifiers therefore reference the same entities at all times. Among others, file-scope functions and extern file-scope variables have external linkage, unless already given internal linkage in the same translation unit.

Qualifiers

The two qualifiers, const and volatile, are sometimes known as cv-qualifiers. They can both be applied to the same element.

const

When applied to a class function, const prevents modifications to class variables in the same instance, unless those are mutable. It also prevents the invokation of non-const class functions in the same instance.

const variables cannot be modified. By extension, const class type instances cannot invoke non-const class functions.

const can appear in two locations within a pointer declaration. Placing it before the type prevents the referenced instance from being modified through the pointer:


const int* op;

Placing it before the variable prevents the pointer itself from being modified:


int* const op = 0;

Placing it in both locations prevents either modification:


const int* const op = 0;

volatile

When applied to a variable or parameter, volatile warns that the value can be read or written without the compiler's knowledge. The compiler is expected to avoid certain optimizations, such as placing the value in a register.

When a class function is declared volatile, that function cannot invoke non-volatile functions on the same instance. Similarly, if a class type instance is declared volatile, non-volatile functions cannot be invoked on that instance, and its variables are considered volatile as well. volatile functions can modify non-volatile variables.

Vexing parse

In general, any statement that can be interpreted as a declaration must interpreted that way. This is known as the most vexing parse. For example, if a class has a default constructor:


class tCacheMem {
public:
  tCacheMem();
  tCacheMem(int aCap);
  ...

it may seem appropriate to invoke that constructor when initializing another class:


class tMgrPath {
public:
  tMgrPath(const tCacheMem& aCache);
  ...

int main(int aCtArg, char* apArgs[]) {
  tMgrPath oMgr(tCacheMem());
  oMgr.Exec();
  ...

In this case, the vexing parse requires that oMgr be interpreted not as an instance of tMgrPath, but as an undefined function that returns an instance of tMgrPath, with tCacheMem() being read as an unnamed parameter for a function with no arguments that returns a tCacheMem. When this happens, oMgr.Exec() will fail with a message that oMgr is not a class type.

The problem can also arise when variables are passed to non-default constructors. In this example, the parentheses around oCap are assumed to be superfluous, so that oMgr is read as a function accepting a tCacheMem parameter named oCap:


  int oCap = 128;
  tMgrPath oMgr(tCacheMem(oCap));

In both cases, the intent can be clarified by surrounding the constructor invocation with parentheses:


  tMgrPath oMgr((tCacheMem(oCap)));

Namespaces

Namespaces define distinct scopes, named or otherwise. They are defined at file scope, or within other namespaces.

Declaring an element in a namespace block places it in that namespace. The element can be defined outside the block, but doing this entails that all namespace members be prefixed with the namespace:


namespace nChain {
  struct tLink;

  tLink gHead();
}

struct nChain::tLink {
  string ID;
};

nChain::tLink nChain::gHead() {
  return nChain::tLink();
}

Distinct namespace blocks with the same name contribute to the same namespace:


namespace nStr {
  string gUp(const string& aText);
}

namespace nStr {
  string gLow(const string& aText);
}

Defining an unnamed namespace issues an implicit using directive. Because such a namespace cannot be referenced anywhere else, its content effectively has file scope:


namespace {
  short gLenMax;
}

gLenMax = 0xFFFF;

Namespace aliasing allows long namespace paths to be abbreviated:


namespace nTransL = nMath::nTrans::nLaplace;

using declarations

A using declaration brings a specific identifier into scope, allowing it to be referenced without specifying its namespace:


using nStr::gUp;

A compilation error results if the identifier is already declared in the current scope. If the identifier is declared in an enclosing scope, that element is hidden.

using directives

A using directive makes an entire namespaces accessible without bringing identifiers into the current scope:


using namespace nStr;

If the same identifier is declared locally and in an accessible namespace, the namespace declaration is hidden. If the same identifier is declared in an enclosing local scope and within an accessible namespace, the declaration in the enclosing scope is hidden. If the same identifier is declared in multiple accessible namespaces (including possibly the global namespace) a namespace must be specified whenever the identifier is used.

The global namespace is always accessible.

Statements

Jump statements

break

break jumps out of the enclosing for, while, do-while, or break statement. Only one such statement is terminated.

continue

continue effectively jumps to the end of the enclosing for, while, or do-while statement, not the beginning. This distinction is important for do-while loops, as continue does cause the loop condition to be retested.

goto

goto jumps to a label in the current function. Labels have function scope, not local scope, so goto can jump into blocks as well as out of them:


if (oDone) goto X;
do {
  ...

  X:
  cout << "Done" << endl;
} while (false);

Every label must be followed by a statement or another label.

goto cannot be used to skip a variable definition unless that variable is a POD type.

Operators

Scope resolution

Applying the scope resolution operator without a name specifies the global namespace:


char gChEsc;
namespace n {
  char oCh = ::gChEsc;
};

Applying the operator with a base class name allows base members to be accessed from derived instances:


struct tProd {
  int Code;
}
struct tBook: public tProd {
  int Code;
}
tBook oBook;
cout << oBook.tProd::Code << endl;

sizeof

The sizeof operator can be applied to instances or types. When applied to an array instance, the size of the entire allocation is returned, this being the size of the array type multiplied by the element count.

Memory operators

It is safe to delete null pointers, but it is not safe to delete dangling pointers, which reference already-deleted memory.

To ensure that the correct destructor is called, a pointer that is being deleted must have the same type as the referenced instance, or its type must be a base class of that type with a virtual destructor.

Types allocated with new[] must provide accessible default constructors. Having been allocated with new[], arrays must be deallocated with delete[]. This causes the destructor to be invoked on each element:


tEl* opEls = new tEl[4];
delete[] opEls;

The standard new operators throw bad_alloc if an allocation fails. The nothrow placement new overloads return zero instead:


char* opBuff = new(nothrow) char[256];

By passing a callback to set_new_handler, it is also possible to have a function invoked when allocation fails.

Ternary operator

The ternary operator evaluates a boolean expression, and returns the second operand if it is true, or the third if it is false. The result can be used as an l-value:


((aAxis == gAxisX) ? cX : cY) = oVal;

Alternatively, if it returns a function pointer, it can be invoked:


((oCt < 1) ? cRun_Front : cRun_Back)(oData);

Other operators

Bitwise XOR is performed with operator^. There is no logical XOR operator, but operator!= is equivalent if both operands are booleans.

The unary sign operators + and - promote their operands to int.

The sequence operator evaluates both operands, then returns the result of the right one. Because this operator has the lowest possible precedence, a sequence expression must be parenthesized when its result is assigned:


int oYOrig = (oX++, oY++);

Functions

An argument is a value that is passed to a function. A parameter is the representation of that value within the function. Arguments can be r-values or l-values, but parameters are always l-values.

Overloading

Overloading allows the same name to be associated with different functions in the same scope. When the overloaded function is invoked, the compiler chooses one definition by comparing the provided arguments with available signatures. If no exact match is found, a near match is chosen, by applying, in order:

1) Type promotions;
2) Standard type conversions;
3) User-defined type conversions;
4) Default function parameters.

If no match is found, a compiler error results.

Note that a child class defines its own scope relative to the base type from which it inherits. When a function is invoked through the child class, the compiler will attempt to match the call to a function in the child scope before it looks outside to functions in the parent. Therefore, if a name in the parent is overloaded in the child:


class tScroll {
public:
  ...
  void Add(float a);
};

class tScrollFast: public tScroll {
public:
  ...
  void Add(int a);
};

calls through the child could be routed to functions that match the arguments less well than functions in the parent:


  tScrollFast oScroll;
  // Truncates to int and calls tScrollFast::Add:
  oScroll.Add(1.1F);

Functions in the parent can be brought into the child scope by using them in the child definition:


class tScrollFast: public tScroll {
public:
  ...
  using tScroll::Add;
  
  void Add(int a);
};

Overloading operators

When a binary operator is defined as a class function, the left operand is represented in the definition by this.

Operator overloads in class types can be declared virtual. All overloads are inherited except the assignment operator.

Overloading assignment operators

If the assignment operator is not overloaded for a given class type, ordinary memberwise assignment will be performed. Overloaded assignment operators are not inherited by subclasses, but the assignment overload in a base class will be called when that part of the derived class is assigned. When overloading the assignment operator, it is best to implement a copy constructor as well.

Overloading increment and decrement operators

When overloading a postfix increment or decrement operator, it is necessary to introduce a dummy int parameter to distinguish the signature from that of the prefix operator. The parameter is not used:


struct ti {
  // Prefix increment:
  ti& operator++() { ++Idx; return *this; }
  // Postfix increment:
  ti operator++(int) { ti oi(*this); ++Idx; return oi; }

  int Idx;
};

Postfix operators are also distinguished by the fact that they return copies, not references.

Overloading memory operators

Like the binary arithmetic operators, new and delete can be overloaded as class or non-class functions. If they are overloaded as class functions, those overloads will be called in preference to any non-class overloads. The class function overloads are implicitly static. Variations like array new and delete, placement new and delete, and nothrow new count as separate operators with distinct overloads.

When an object is allocated, its size is passed automatically to operator new. Similarly, the size of the entire array is passed to operator new[]. When an object is deallocated, the pointer is passed to operator delete:


struct tMgr {
  void* operator new(size_t aSize);
  void operator delete(void* ap) {}
};

void* tMgr::operator new(size_t aSize) {
  if (aSize > sizeof(tMgr)) terminate();

  static tMgr osInst;
  return &osInst;
}

Any number of parameters can be added to new or delete. Operators with extra parameters are known as placement new and delete:


struct tMgr {
  tMgr() {}
  tMgr(const string& aID): ID(aID) {}

  string ID;

  void* operator new(size_t aSize, int aIdx);
  void operator delete(void* ap, int aIdx) {}
  void operator delete(void* ap) {}
};

void* tMgr::operator new(size_t aSize, int aIdx) {
  if ((aIdx < 0) || (aIdx >= 4)) terminate();

  static tMgr osInsts[4];
  return &osInsts[aIdx];
}

If an exception is thrown during initialization, the placement delete overload with the same additional parameters will be called. If such an overload is not defined, delete will not be called, and a leak may result. The placement delete overload is not called when memory is explicitly deleted.

To invoke placement new, pass the additional arguments to the new operator itself. The allocation size will be passed automatically:


tMgr* opMgr = new(0) tMgr("A1");

Exceptions

All standard exceptions derive from exception, but any type can be thrown.

If an exception is thrown within a try block, the first catch block that matches the exception type will be executed, even if a succeeding block matches more closely. If no block in the call stack matches the exception, terminate will be called. By default, terminate ends the program, but a custom handler can be set by passing a callback to set_terminate.

Caught exceptions can be re-thrown by invoking throw without an argument.

If the stack is being unwound to handle one exception when a second is thrown, the runtime will be unable to choose which of the exceptions to propagate, and terminate will be called. For this reason, destructors should never throw exceptions.

A function may be designed to meet one of the three exception guarantees, which characterize its effects in the event that it cannot complete successfully:

  • The basic guarantee claims that all objects will be left in internally-consistent and usable states, and that no resources will be leaked;
  • The strong guarantee claims that the function will not change the program's state if it fails;
  • The no-throw guarantee claims that the function will never fail.

A function that makes no guarantee may leave the program in an unusable state.

Function try blocks

A function try block replaces the body of a function with try and catch blocks, so that the entire function is covered by the catch:


void gExec()
try {
  ...
}
catch (const tExcept& aExcept) {
  gLog.Add(aExcept);
}

This is the only way to catch exceptions thrown from a constructor's initializer list.

Exception specifications

An exception specification limits the types that can be thrown from a given function. The same specification must be applied to the function's declaration and its definition:


void gVeri() throw(bad_alloc, bad_typeid);

If an exception is thrown from a function with such a specification, and if its type is neither specified nor derived from a specified type, unexpected will be called. By default, unexpected ends the program, but a custom handler can be set by passing a callback to set_unexpected. The specification is checked only when exceptions are thrown out of the function. Any type can be safely thrown and caught within it.

When a function is overridden, it must specify the same specification that was declared in the base class, or a more restrictive one.

If no type is listed in the specification, no exception can be thrown from the function:


void gSort() throw();

Templates

Template classes

The template keyword introduces a template parameter list. Like a function parameter list, this construction identifies parameters to be used in the definition:


template <typename xType, int xSize>
struct tArr {
  tArr(int aVal);

  xType Els[xSize];
};

In template classes, the class name is followed by a template argument list, which identifies the specialization to be defined:


template <typename xType, int xSize>
inline tArr<xType, xSize>::tArr(int aVal) {
  for (int o = 0; o < xSize; ++o)
    Els[o] = aVal;
}

Often this reiterates the parameter list, but in partial or explicit specializations, some or all parameters will be replaced with specific types.

Template functions

Template functions are defined much as template classes are, but no argument list is needed for the general specialization:


template <typename xType>
void gExch(xType& arVal0, xType& arVal1) {
  xType oVal(arVal0);
  arVal0 = arVal1;
  arVal1 = oVal;
}

Nor is the argument list always needed to invoke the function:


gExch(oX, oY);

It can be used to select a particular specialization, however:


gOut<string>("Ready");

It is also used to pass non-type parameters:


gExec<256>(od, 1000);

Template functions can be defined in class types that are not themselves template classes:


struct tFile {
  template <typename xData>
  void Write(xData& arData);
};

template <typename xData>
inline void tFile::Write(xData& arData) {
  eWrite(arData.Read());
}

Template parameterization

Templates can be parameterized with type or non-type parameters. Default arguments can be specified for either:


template <typename xType = short, int xDim = 3>
struct tPt {
  static const int sDim = xDim;
  xType Disp[xDim];
};

Parameters that follow the first default argument also require defaults. The argument list braces must be applied to a template class, even if all the defaults are accepted:


tPt<> oPt;

Type parameters

Type parameters must be preceded by typename or class. These are equivalent.

Class types with local scope cannot be used as type arguments.

Non-type parameters

Non-type parameters are defined with a type and a parameter name, much like function parameters, though they do not pass arguments in the same way. Template arguments must be constant.

Non-type parameters can be:

  • Integer types or enumerations;
  • References to variables with external linkage;
  • Pointers to variables or functions with external linkage;
  • Pointers to class variables or non-overloaded class functions.

String literals are not valid template arguments.

Template specialization

Explicit specialization

Explicit specialization allows distinct implementations to be defined for specific parameter combinations. Explicitly-specialized template classes can define different variables and functions; they need share nothing, in fact, but their names and template parameter signatures.

To distinguish an explicit specialization from other specializations of the same template, the entire argument list must be populated with specific types. Since every template parameter has been specified in the argument list, the class definition is preceded by an empty parameter list:


template <>
struct tArr<bool, 1> {
  tArr(int aVal);
  
  bool El;
};

The same is done for non-class function specializations:


template <>
void gExch<int>(int& arVal0, int& arVal1) {
  arVal0 ^= arVal1;
  arVal1 ^= arVal0;
  arVal0 ^= arVal1;
}

No parameter list is needed for class function specializations:


inline tArr<bool, 1>::tArr(int aVal) {
  El = (aVal != 0);
}

Partial specialization

Partial specialization supports distinct implementations for certain template parameter choices, while leaving other parameters open.

The open parameters remain in the template parameter and argument lists. The constrained parameters are removed from the parameter list, and their entries in the argument list are replaced with specific types:


template <int xSize>
struct tArr<bool, xSize> {
  tArr(int aVal);

  bool Els[xSize];
};

template <int xSize>
inline tArr<bool, xSize>::tArr(int aVal) {
  for (int o = 0; o < xSize; ++o)
    Els[o] = (aVal != 0);
}

Template definition

typename is most often found in a template parameter list, but it can also be used in the template definition, where it explicitly marks some element as a type:


template <typename xType>
void gNext() {
  typename xType::tData oData;
  gExec(oData);
}

The compiler knows nothing about a given parameter type until the template is specialized, so it may not otherwise be able to parse the definition. Visual Studio does not require typename in this particular case, however.

Preprocessing

After preprocessing, source files are known as translation units or compilation units. The compiler transforms these into object files.

Directives

Generally, the pound sign in a preprocessor directive must be the first non-whitespace character in its line. Visual Studio does not enforce this requirement.

Preprocessor directives can be made to span multiple lines by appending a backslash to the end of each continued line.

#define

#define can be used to create a macro, which associates a name with the text that follows it:


#define mLvlOpt 4

The preprocessor will replace the name with the text wherever it is found. Even keywords can be used as names.

The name can be followed by an untyped parameter list. These parameters can then be referenced within the text, much like a function:


#define mMaskComb(z) (~(z) & 0x0F0F)

It is customary to parenthesize parameters within the definition to avoid operator precedence problems when expressions are passed to the macro. Unlike ordinary function calls, macro parameters are evaluated every time they appear in the text, so expressions with side effects must be avoided.

If no value follows the name, the name will be defined, but it will be replaced with the empty string during preprocessing:


#define mBuildFast

#undef cancels the effect of a #define directive.

Conditional directives

#ifdef and #ifndef test whether a preprocessor identifier has been defined. #if and #elif test whether a constant expression evaluates to true. Any of these can be followed by #else, and all must be followed at some point by #endif.

#include

Included files are always located in an implementation-specific manner. Generally, files in angle brackets are located by searching a user-defined path:


#include <iostream>

Filenames in double quotes are typically sought in the folder containing the file performing the include:


#include "Test.h"

Visual Studio then searches folders containing files that themselves include the file performing the include. Most implementations finally locate the file as though its name were enclosed in angle brackets.

#error

Compilation is aborted if the preprocessor encounters an #error directive. If a string follows #error, it will be displayed by the compiler.

#pragma

#pragma provides access to implementation-specific compiler features.

#line

#line is used to reset the preprocessor's record of the line number:


#line 1

or its record of the line number and file name:


#line 1 "Test.h"

Macro operators

#

The # operator wraps a macro argument with double quotes:


#define mOut(zText) cout << "~ " #zText << endl;

##

The ## operator concatenates two macro arguments into a single word:


#define mJoin(z0, z1) z0##z1

Predefined macros

A number of macros are defined by the environment:

Identifier Value
__cplusplus Defined if the code is being compiled as C++
__LINE__ The number of the line being preprocessed
__FILE__ The name of the file being compiled
__DATE__ The compile date
__TIME__ The compile time

Miscellanea

RTTI

When enabled, Run-time Type Information or RTTI allows very basic type information to be retrieved while the program runs.

The typeid operator returns a const reference to a type_info instance:


#include <typeinfo>

const type_info& oInfo = typeid(string);

The type_info class identifies types. It supports type comparisons with operator== and operator!=, and it provides a name function that returns a char array containing the type name. The exact name output is implementation-defined.

The main function

The main function accepts zero:


int main()

or two arguments:


int main(int argc, char* argv[])

argv[0] stores the name of the executable, with or without its path. argv[argc] is set to zero.

The value returned from main becomes the return value of the executable. In Visual Studio, failing to return from main does not generate a warning, unlike other functions, and doing this causes zero to be returned from the executable. Programs can also be terminated with exit, which accepts a return value.

Sources

C++ Pocket Reference
Kyle Louden
2003, O'Reilly Media

Effective Modern C++
Scott Meyers
2014, O'Reilly Media

The C++ Programming Reference, Third Edition
Bjarne Stroustrup
1998, Addison-Wesley

The C++ Programming Reference, Fourth Edition
Bjarne Stroustrup
2013, Addison-Wesley

Scope Regions in C and C++
Dan Saks
November 2008, www.embedded.com

Linkage in C and C++
Dan Saks
November 2008, www.embedded.com

volatile - Multithreaded Programmer's Best Friend
Andrei Alexandrescu
November 2008, www.ddj.com

Stack Overflow question 'Hidden Features of C++?'
July 2015, stackoverflow.com

Exception Safety and Exception Specifications: Are They Worth It?
Herb Sutter
June 2018, www.gotw.ca