“How can anyone view the trashing of our founding tradition as evidence of patriotism? Because some have adopted a very different political philosophy than the Founders held. This approach to government promises the recovery of a mythical past. It feeds a sense of White victimhood. It emphasizes emotion over reason. It denigrates experts and expertise. It slanders outsiders and blames them for social and economic ills. It warns of global plots by Jews and shadowy elites. It accepts the lies of a leader as a deeper form of political truth. It revels in anger and dehumanization. It praises law and order while reserving the right to disobey the law and overturn the political order through violence.

...[Trump] and a significant portion of his supporters have embraced American fascism.”

— Michael Gerson in The Washington Post

C++ Notes

Programming language notes — C++03-17

C++ Notes

These are my C++ notes. They document C++03, plus some features from C++11-17, which is generically labeled modern C++. The notes do not provide an exhaustive description of the language. Concepts that seem obvious to me often go unmentioned. Conversely, some of these items are interesting without being particularly useful. 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, change the Scale setting in your browser’s print options to 70%, and enable background graphics. Firefox and Chrome each have their own set of printing bugs, but one of them usually works.

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 also 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

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 the same operator, the integer is automatically converted to a float.

References

In general, an l-value is an object that can be addressed through a variable or pointer. Parameters are always l-values, even when they reference r-values. An r-value is typically a temporary object. In many cases, the content of an r-value can be moved rather than copied, since the r-value will not be used again.

L-value references

Unless otherwise specified, a reference in C++ is an l-value reference, declared with a single ampersand. This is generally made to reference an l-value. If the reference is non-const, it must reference an l-value:

static int eCtFull = 0;
static int eCtDef = 16;

void gReady(bool aCkFull) {
  int& orCt = (aCkFull ? eCtFull : eCtDef);
  ...

However, if the reference is const, the reference can be initialized with a temporary object. When this is done, the temporary is made to persist until the reference goes out of scope:

const tPt& oPtCtr = aCkOrig ? tPt() : tPt(aX, aY);

After initialization, a reference cannot be made to target a different object.

R-value references

Just as a non-const l-value reference targets an l-value, an r-value reference in modern C++ targets an r-value. If an r-value reference is initialized with an l-value, a build error will result, just as if a non-const l-value reference were initialized with an r-value.

The r-value reference is declared with two ampersands. It can be used to define move constructors and move assignment operators that swap instance content without copying:

class tAct {
public:
  tAct(tAct&& arAct) {
    Swap(arAct);
  }

  tAct& operator=(tAct&& arAct) {
    Swap(arAct);
    return *this;
  }

  void Swap(tAct& arAct) {
    tDataAct* opData = epData;
    epData = arAct.epData;
    arAct.epData = opData;
  }
  ...

The source instance is modified, so it cannot be const, as it would in a copy operation. The source should be left in a destructible state, and it should be assignable, if the type supports assignment. Move operations should not throw.

Defining copy and move operations allows a type to copy from l-values and move from r-values. Additionally, the move template function in the Standard Library casts an l-value to an r-value reference, allowing a move to be performed on l-values:

class tPlan {
public:
  tPlan(tPlan&& arPlan):
    eActDef(move(arPlan.eActDef)) {
  }

  tPlan& operator=(tPlan&& arPlan) {
    eActDef = move(arPlan.eActDef);
    return *this;
  }

  void Swap_Def(tAct&& arAct) {
    eActDef = move(arAct);
  }
  ...

If move is applied to a type that does not implement move operations, the instance will be copied.

Note that swap operations can be implemented with any non-const reference, whether l-value or r-value. Defining a swap parameter as an r-value reference ensures that the argument is a temporary object, or an instance explicitly wrapped with move.

While it can be useful to move a parameter representing an r-value to a function's return value:

tAct gAdv(tAct&& arAct) {
  tDataAct oData = gDataAdv();
  arAct.Upd(oData);
  return move(arAct);
}

it is not necessary to move a local variable to the return:

tAct gWait() {
  tAct oAct;
  ...
  return oAct;
}

This return value optimization is performed automatically by the compiler.

Forwarding references

Applying two ampersands to a deduced type can produce a forwarding reference (also known as a universal reference) rather than a true r-value reference. Specifically, given type parameter x in a template function, the parameter type x&& produces a forwarding reference if it is not qualified with const or volatile:

template<typename x>
void gExec(x&& ar) {
  ...

auto&& also produces a forwarding reference:

auto&& orLot = aLot;

Unlike r-value references, which must be initialized with r-values, forwarding references can be initialized with r-values or l-values. They can also target const or volatile instances. When that happens, the qualifiers are captured within the type parameter, so the forwarding reference cannot be used to bypass their effects, even though it does not list the qualifiers.

The forward function casts a reference to an r-value if it represents an r-value argument. This allows the argument to be perfect-forwarded to other functions as an r-value or as an l-value, with qualifiers:

void gPrep(const tAct& aAct) {
  ...
}

void gPrep(tAct& arAct) {
  ...
}

void gPrep(tAct&& arAct) {
  ...
}

template<typename xAct>
void gRun(xAct&& aAct) {
  gPrep(forward<xAct>(aAct));
  ...

It is dangerous to move a forwarding reference, because move unconditionally casts to an r-value reference, even if the reference happens to represent an l-value.

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. Like class variable pointers, these reference a specific class type in addition to the 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.

Enumerations

An enumeration associates a set of named integral values with a new type:

enum tSt {
  gStReady,
  gStAct = 100,
  gStDone
};

The elements are called enumerators. These often have the size of an int, but the standard allows them to be larger if their values are outside that range. Visual Studio warns and then truncates values when this happens.

Unless a value is specified, each enumerator takes the value of its predecessor, plus one. If no value is specified for the first enumerator, it is set to zero. Multiple enumerators can have the same value.

Enumerations can be defined anonymously. They can have namespace, class, or local scope:

void gOut() {
  enum {
    oOpen = 10,
    oClosed = 20
  };
  ...

Ordinary enumerations produce unscoped enumerators, which have the same scope as the enumeration itself. These enumerators are automatically converted to integral types, which in turn are converted to floats. Integral types cannot be converted to enumerators without a cast.

Enumeration classes

In modern C++, placing the class keyword after enum produces an enumeration class:

enum class tSt {
  Ready,
  Act = 100,
  Done
};
Enumeration classes create scoped enumerators that differ from unscoped enumerators in two ways:
  • They do not appear in the scope containing the enumeration. To reference one, it is necessary to prefix with the enumeration name, like a static class member:

    tSt oSt = tSt::Ready;
    
  • They are not converted automatically to integral values.

In modern C++, it is possible to specify the underlying integral type by listing it after the enumeration name, much like the base class in a class type definition:

enum tSlot: char {
  ...

It is also possible to forward-declare an enumeration, allowing the definition to be moved to a CPP file:

enum tSlot: char;

Enumeration classes can be forward-declared without specifying an integral type. According to the standard, traditional enumerations must specify a type when they are forward-declared, but Visual Studio ignores this requirement.

Class types

Class types include classes, structures, and unions. Though they are usually defined with file or namespace scope, they can be defined locally, as long as all functions are defined within the class definition:

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

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

  tNode oNode;
  ...

Constructors and destructors

A constructor is called when storage is allocated. If no constructor is accessible at some point in the code, no instance can be allocated there, dynamically or otherwise. The constructor initializes class variables in the order of their definition within the class, not their order within the member initializer list.

Class types that implement destructors cannot be automatically or statically allocated if their destructors are inaccessible. 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* opEl = new tEl;

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

tEl* opElZero = new tEl();

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.

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;

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 are called even if the instance is deleted through a base class pointer.

Special member functions

By default, the compiler implements a number of special member functions for class types, if such functions are invoked anywhere in the code:

  • A default constructor, unless the type defines a constructor of its own, default or otherwise;
  • A copy constructor that performs a shallow copy;
  • A copy assignment operator that also performs a shallow copy;
  • A destructor.

To implement the default constructor automatically, it must be possible to default-construct every variable in the class type. Similarly, to implement the copy constructor, every variable must itself provide an accessible copy constructor. The same holds for the assignment operator.

The Rule of Three recommends that, if either the copy constructor, copy assignment operator, or destructor is explicitly defined, all three should be defined, since custom resource management in one function entails a similar obligation in the others.

In modern C++, the compiler also defines a move constructor and a move assignment operator. Unlike the original special member functions, defining either of these prevents the other from being implemented automatically. Additionally, defining a copy constructor, a copy assignment operator, or a destructor prevents either move function from being implemented by the compiler. Conversely, the original copy functions are not implemented if either move function is defined.

If the default implementation for any special function is disabled, it can be recalled by applying the default keyword to its declaration:

class tLot {
public:
  virtual ~tLot();

  tLot(tLot&& aLot) = default;
  tLot& operator=(tLot&& aPt) = default;
  ...

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 integral 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 integral 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 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.

Access levels

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

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

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();
  ...

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;

Deleting functions

It is often desirable to prevent the compiler from defining its own copy constructor and assignment operator for a particular class type. Traditionally, this is done by declaring the functions private and omitting their definitions.

In modern C++, this intention can be made explicit by applying the delete keyword to the declarations:

class tMgrRsc {
public:
  tMgrRsc(const tMgrRsc&) = delete;
  tMgrRsc& operator=(const tMgrRsc&) = delete;
  ...

By itself, each declaration prevents the compiler from defining the function. Additionally, the delete keyword causes a compiler error to be produced if the function is called, rather than a linker error, as would result if the definitions were simply omitted.

In other respects, the delete syntax produces an ordinary declaration. In particular, it is not possible to delete functions that have already been declared or defined. delete can be applied to different signatures, however. If a function accepts a type like int, to which many other types are automatically converted:

int gNextID(int aID) {
  ...

the unwanted conversions can be avoided by targeting alternative signatures with delete:

int gNextID(char) = delete;

The compiler recognizes the deleted function as an overload, so it does not even consider the conversion. delete can also be applied to unwanted template signatures or specializations:

template <typename x>
void gStore(x a) {
  ...
}

template <typename x>
void gStore(x* a) = delete;

Virtual functions

Declaring a class function virtual:

class tWin {
public:
  virtual void Inval(thRend ahRend);
  ...

allows derived classes to override it:

class tView: public tWin {
public:
  void Inval(thRend ahRend);
  ...

When this is done, the new implementation is used wherever the function is called, even in the base class. By contrast, non-virtual functions can only be hidden, which causes the new implementation to be invoked from the derived class, but not from the base. Class types with at least one virtual function are said to be polymorphic. Because the correct implementations are not known until run time, virtual functions cannot be inlined by the compiler.

Though it remains inaccessible, a private base class function can be overridden, causing the derived implementation to be called when invoked by the base. The base implementation remains inaccessible to the child.

override

To override successfully, the base function must be virtual, and the new function must match its name and signature exactly. If these requirements are not met, the result is typically a new function that perhaps hides or overloads the original. Compilers seldom warn when this happens.

In modern C++, functions can be marked override, in the same way they are marked const or volatile:

class tEd: public tWin {
public:
  void Inval(thRend ahRend) override;
  ...

Such a function is meant to override a function in the parent. If it fails to do so, a compiler error results.

final

Where virtual allows a function to be overridden, in modern C++, final prevents a function from being overridden any further:

class tEdNum: public tEd {
public:
  void Inval(thRend ahRend) override final;
  ...

A class type can also be made final by placing the keyword after the class name, and before the inheritance list:

class tEdNumCust final: public tEdNum {
  ...

Such a class cannot be subclassed at all.

Pure virtual functions

After declaring one or more pure virtual or abstract functions:

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

a class 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:

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.

Other types can use initializer lists in modern C++, but aggregates can use them in all versions.

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.

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:

  • Integral 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.

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 xEl>
using tSeqsByCd = map<string, vector<xEl>>;

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 is used to perform upcasts and downcasts. It does not use RTTI, it does not require a polymorphic source type, and it performs no run-time check, so it cannot return zero or throw, but it can produce invalid casts. It should not be used unless the instance type is already known. 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 integral 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.

A forward declaration allows a variable, function, or type to be referenced before it is defined. In particular, a forward-declared type can be referenced as long as its members are not accessed, and its size not calculated:

struct tNode;

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

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

Variables marked const cannot be changed after they are initialized. 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;

When a class function is marked const, it is prevented from modifying variables in the instance from which it is called, unless those variables are mutable. It is also prevented from calling other functions in the same instance, unless they are const. Non-const class functions cannot be invoked from const instances.

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.

If 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.

constexpr

Modern C++ allows variables and functions to be declared constexpr.

When applied to a variable, constexpr indicates that its value can be known at compile time. constexpr variables are implicitly const, but const variables are not constexpr because they can be assigned with values that are not known until run time.

When applied to a function, constexpr indicates that it can be evaluated at compile time, if its arguments are themselves constexpr. Such functions can also be called at run time, like other functions. Both class and non-class functions can be constexpr. Note that constexpr appears at the beginning of the declaration, not the end, as const does:

struct tPos {
  constexpr tPos(double aR, double aI);
  constexpr double Dist() const;
  ...

Implementing a constexpr constructor produces a literal type, which can be instantiated at compile time, like built-in types.

Only pure functions can be constexpr. These are functions that have no side effects, and which return values that are fully determined by the function's inputs. In C++11, constexpr functions must consist of a single return statement. In C++14, functions can contain multiple statements, but they cannot contain try blocks, goto statements, or static variables, and all variables must be literal types that are initialized where they are defined. In all cases, the parameters and return type must also be literal.

constexpr class functions are implicitly const in C++11, but not in C++14, even though side effects are disallowed. These must be explicity marked both constexpr and const.

constexpr values can be used in places that are normally reserved for compile-time constants, like array sizes and enumerator values.

Vexing parse

In general, any statement that can be interpreted as a declaration must be 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)));

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 also return copies, rather than 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");

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.

Conditional operator

The conditional or 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++);

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.

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 not throw exceptions.

A function may be designed to meet one of the three exception guarantees, which characterize its effects if it cannot complete its task:

  • The basic guarantee claims that all objects will be left with 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:

void gVeri() throw(bad_alloc, bad_typeid);

and its definition:

void gVeri() throw(bad_alloc, bad_typeid) {
  ...

If an exception is thrown from such a function, 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.

If the function is overridden, it must provide 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();

noexcept

Traditional exception specifications are deprecated in modern C++. They have been replaced by noexcept, which merely indicates whether exceptions can be thrown. This allows compiler and library implementers to optimize code without breaking exception guarantees. When applied without arguments, noexcept claims that a function does not throw:

void gVeri() noexcept;

As before, the same specification must be applied to the function's declaration and its definition. noexcept can also accept a constant expression argument. If the expressions is true, no exceptions are meant to be thrown:

template <typename x>
void gStore(x aVal) noexcept(is_pointer(x)) {
  ...

By default, all destructors and delete operators are implicitly noexcept.

The noexcept operator is often used inside the noexcept specification. This operator accepts a single expression argument, and returns true if the compiler can verify that the expression does not throw:

class tSigFast {
public:
  void Exec() const noexcept;
  ...

template <typename xSig>
void gForw(xSig aSig) noexcept(noexcept(aSig.Exec())) {
  ...

If a noexcept function does throw, terminate will be called.

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.

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

Standard Library

Smart pointers

auto_ptr

auto_ptr is the original Standard Library smart pointer. It overloads operator* and operator-> so that instances can be dereferenced like regular pointers:

auto_ptr<tCase> oCase(new tCase());

tCase oCaseOrig(*oCase);
oCase->Adv();

Copying one auto_ptr to another causes the source instance to release ownership of the allocation, leaving the source to reference nothing. As a result, it is unsafe to store auto_ptr instances in most containers, and const instances cannot be copied.

auto_ptr is deprecated in modern C++. It is replaced by unique_ptr, shared_ptr, and weak_ptr.

unique_ptr

unique_ptr ensures that a single smart pointer owns the allocation at any moment. Instances cannot be copied; ownership is instead transferred when an instance is moved. Unlike auto_ptr, unique_ptr provides a boolean conversion that returns true if the pointer is set. This allows instances to be checked in boolean expressions like raw pointers:

void gAttach(const unique_ptr<tPack>& abPack) {
  if (!abPack) ...

The specialization for array allocations implements operator[], so elements can be dereferenced as normal:

unique_ptr<tPt[]> obPts(new tPt[oCt]);
for (int o = 0; o < oCt; ++o) {
  tPt& orPt = obPts[o];
  ...

C++14 provides the make_unique function that produces unique_ptr instances without calling new directly:

const tPt oPtDef(oX, oY);
unique_ptr<tPt> obPt = make_unique<tPt>(oPtDef);

shared_ptr

shared_ptr is a reference-counting smart pointer, which allows a single allocation to be shared among multiple instances. The reference count is set to one when the raw pointer is first stored in a shared_ptr. The count is incremented whenever an instance is copy-constructed or copy-assigned, and decremented when an instance is destroyed. The allocation is deleted when the count reaches zero. Reference cycles must be avoided, or the allocation will not be freed. It is also important that the original raw pointer not be passed to more than one shared_ptr, except by copying or assigning smart pointer instances. If this happens, one group of instances will delete the allocation while the others are still active, causing them to dangle.

Unlike unique_ptr, shared_ptr cannot be used with array allocations.

C++11 provides the make_shared function that produces shared_ptr instances without calling new directly.

weak_ptr

weak_ptr can be used to reference a shared_ptr allocation without incrementing the reference count. This allows references to cycle without blocking deallocation. In particular, when implementing the Observer pattern, the publisher can be made to reference subscribers with weak_ptr instances. Subscribers can then be deleted without explicitly unsubscribing.

A weak_ptr instance is created by passing a shared_ptr to the weak_ptr constructor. weak_ptr provides no way to access the allocation, so another shared_ptr must be created. This can be done by:

  • Calling the lock method, which returns a shared_ptr that references the original allocation, or null if the allocation has been deleted;
  • Passing the weak_ptr to a shared_ptr constructor, which throws if the allocation has been deleted.

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
Retrieved November 2008, www.embedded.com

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

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

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

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

"constexpr" function is not "const"
Andrzej Krzemieński
Retrieved July 2018, akrzemi1.wordpress.com