The flag belongs to everyone, not just veterans, but not everyone in this country is treated equally. That's what black football players are protesting, and that's what conservatives refuse to acknowledge, or even discuss. Instead they hide behind the flag. Conservatives aren't 'respecting' veterans when they do this, they're using veterans to draw attention from their own selfishness and racism.

A recent poll shows that Republicans are more likely to hold a negative opinion of President Obama (81%) than they are of Vladimir Putin, a warmongering tyrant who consistently opposes US interests (47%).

I'm not a big Obama fan myself, but these people have lost their fucking minds.

C++ Notes

Detailed language notes — C++03

C++ Notes

Compiled by Jeremy Kelly
www.anthemion.org

These are my personal C++ notes, covering C++03. They are here primarily 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 use the 'scale' setting in your browser's Print dialog reduce the font size.

Contents

Basic types

Characters

Character types are generally signed by default.

char specifies an integer representing one element from a single character set, usually but not necessarily ASCII; it is one byte in size.

wchar_t specifies a type representing 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';

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. Each type is less than or equal to the following type in size:

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

Integer literal modifiers:

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 values are called enumerators. The size of an enumerator is normally that of an int, but may be larger if its value is outside the range of an int. Visual Studio warns and then truncates explicit enumerator values outside this range.

An enumerator can be used anywhere an int is used; however, an int cannot replace an enumerator without being 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. Each type is less than or equal to the following type in size:

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 some operator, the integer is converted to floating-point before the operation proceeds.

References

If it returns a non-const reference, a function's return value can be used as 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.

Class data pointers

A pointer can be declared to reference a variable within some class type. Such a pointer does not reference a particular variable instance, as other data pointers do; instead, it references one member within any instance of the containing type. In this it resembles 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 data pointer, but the indirection operator is prefixed by the class type. Typedefs are defined similarly, with the typedef name replacing the pointer name:


typedef int tEnv::* tpSize;

Since class data pointers do not reference specific class type 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 as any class 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 not:


odCt = gCt;

Function pointer typedefs are defined similarly, with the typedef name replacing the pointer name:


typedef short (* td)(int);

Once initialized, function pointers can be invoked directly:


short oCt = odCt(100);

or they can be dereferenced first:


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, so long as the signatures match:


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

odCt = &tCat::sCt;

Non-static class function pointers

Pointers can also be made to reference non-static class functions. Much as class data pointers do, each such pointer references 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 data pointers, the indirection operator is prefixed with the class type. Typedefs are defined analogously:


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

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 any class function 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];

or the equivalent:


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 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 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 it is defined within a block.

Typedefs

A typedef defines an alternative name for an existing type. Unlike macros, a typedef lists the new name after the definition. Typedefs can be defined with local scope:


#define mCtSeqPort 12

void gEnum_Ports() {
  typedef vector<int> tSeq;
  
  tSeq oSeq(mCtSeqPort);
  ...

Class types

Class types include classes, structures, and unions.

Namespaces, enumerations, typedefs, and other class types can be nested within class definitions, just as variables and functions are. 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 types to be referenced before they are defined, so long as their members are not accessed and their sizes 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 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. Such variables are initialized before main executes. Their relative initialization and deinitialization order is undefined.

Non-static class variable declarations serve also as definitions. Static class variable declarations do not serve as definitions, so their variables must be defined elsewhere. Technically, even static const integral variables require explicit definition, even though these can be 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 of a given field cannot exceed that of the type with which it is defined. 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 such variables, fields defined within unions share memory with other variables.

Class functions

Static class functions

Static class functions are not associated with a particular instance of the class. As a result, this is not defined within static functions.

const class functions

const functions are allowed to modify static class data. They cannot modify other variables in the same instance, except those declared mutable.

volatile class functions

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 some point in the program, 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

Constructors with no non-default parameters qualify as default constructors. The function call operator is not required when invoking a default constructor:


tEl oElTemp;
tEl* opEl = new tEl;

However, supplying 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 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

Copy constructors accept a reference to an instance of the containing class type. They can accept additional parameters if default values are provided for them. When implementing a copy constructor, it is advisable to overload the assignment operator as well.

When a class type variable is initialized with an instance of the same type, the copy constructor is called, not operator=:


tIt oItTemp = oIt;

If no copy constructor is explicitly defined for some class type, the compiler will create one that calls the copy constructor of each variable in the type, as long as all variables have accessible copy constructors.

Construction and destruction with inheritance

When a derived class is instantiated, the constructors of its least-derived base classes are invoked first. When it is destroyed, its destructor and those of its most-derived base 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 included in the derived class member 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 base class destructors virtual ensures that derived destructors are called even when instances are deleted through base class pointers.

Inheritance

Hiding

Just as base class functions are hidden by derived class functions with the same name and signature, base class variables are hidden by derived class variables with the same name. Base members can be accessed from derived classes by using the scope resolution operator:


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;

In both cases, the identifier is prefixed with the name of the targeted class type.

Virtual functions

Declaring a class function virtual allows derived classes to override it. Non-virtual functions can only be hidden by derived classes. Class types with at least one virtual function are said to be polymorphic.

Though they are not accessible from a derived class, private base class functions can be overridden in derived classes, causing the derived implementations to be called when invoked by the base. The base implementations remain private.

Virtual functions are never inlined by the compiler.

Pure virtual functions

Classes declaring one or more pure virtual functions cannot be instantiated; their descendents cannot be instantiated either, until all such functions have been overridden. In all other respects, pure virtual functions resemble other class functions; they can even be defined 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 fact, pure virtual destructors — used sometimes to render classes with no other functions abstract — must be defined if derived classes are to be instantiated.

Inheritance access levels

Inheritance access levels 'cap' the accessibility of base class members, with protected inheritance rendering public base members protected in the inheriting class, private inheritance rendering all base members private, and public inheritance leaving all base members unchanged. The changes are enforced relative to the inheriting class, so private inheritance allows base members to be accessed by the inheriting class, but not by descendents of that class:


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

When a class derives from multiple base classes, and two or more of the base classes share a common ancestor, multiple instances of that ancestor are defined in the derived class. To avoid ambiguity, it is necessary to specify an intermediate class when referencing duplicated variables and functions.

To avoid such duplication, the common ancestor can be inherited as a virtual base class:


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. Reading from a member other than the last one written to produces undefined behavior.

A union cannot contain any variable that implements a constructor or a destructor, or that overloads the assignment operator; doing so would cause ambiguity when the union itself is constructed, destroyed, or assigned. Unions cannot participate in inheritance or declare virtual functions.

Unions declared within other class types can be anonymous:


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

Variables within such 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 are 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 conversion

A conversion from a given user type to another type is implemented with a conversion operator. A conversion from another type is implemented with a constructor that takes that type as a parameter.

Conversion operators

The conversion operator declaration is unlike that of 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 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;

Conversion operators, when defined, are also invoked during explicit conversions:


cout << (int)oOrd;

Conversion by construction

If a constructor can be invoked with a single parameter, and if it is not declared explicit, 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;

If the constructor is marked explicit, the same 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 multiple matching base instances are found (because multiple inheritance was used without a virtual base class) 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, which is safe only if the data being cast was not originally declared with such qualifiers:


struct tCurs {
  int Idx;

  void Skip() const;
};

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

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 single memory location is referenced by two or more lvalues, whether these are variables, pointers, or references. When this is done, the compiler must perform extra work to maintain consistency, since a write to one alias must be reflected in all the others. To minimize this work, C and C++ enforce strict aliasing rules to limit the number of aliases that are considered valid and which therefore require updates. In C++, an lvalue is a valid alias if either:

  • It has the same base type as the referenced object, after disregarding cv-qualifiers and signed/unsigned distinctions. This includes lvalue types that are parents of the referenced object type;
  • It is an aggregate type or a union that includes one of the above types, either directly or within a recursively contained aggregate or union;
  • It is a pointer or reference to char or unsigned char.

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

RTTI

typeid

The typeid operator returns a const reference to a type_info instance representing the specified type:


#include <typeinfo>

const type_info& oInfo = typeid(string);

type_info

The type_info class identifies types. It supports instance comparisons with operator== and operator!=, and implements the name function that returns a char array giving the type name. The exact output of type_info::name is implementation-defined.

Declarations

Declarations introduce identifiers to translation units; some also define elements.

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 variable declaration also serves as a definition if storage is allocated, which occurs for all but class-static variable declarations, and extern declarations that lack initializers.

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

Scope

An identifier's scope determines the parts of a translation unit from which the identifier is accessible.

Namespace scope

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

The global namespace is unnamed, and is not explicitly defined; it contains all other namespaces. Identifiers in this namespace are said to have file scope.

Function scope

A labels has function scope, making it usable throughout a function, even before it is declared. This allows goto to jump into or out of blocks. No other identifier has this scope.

Local scope

A non-label identifier declared anywhere within a function has local scope. It is usable after its declaration in the block that declares it.

Class scope

An identifier declared within a class type declaration and outside of any function definition has class scope. Such an identifier also has a public, protected, or private access level, but this is not a scope as such. An identifier with class scope is usable anywhere its access level permits.

Linkage

Linkage determines whether distinct declarations using the same identifier reference the same entity, or different ones.

No linkage

Elements referenced by identifiers with no linkage cannot be referenced from multiple scopes; such identifiers therefore reference different entities when used in different scopes. Local variables have no linkage.

Internal linkage

Elements referenced by identifiers with internal linkage can be accessed from multiple scopes, but not from multiple translation units; such identifiers therefore reference different entities when used in different translation units. Variables and functions with file scope that are declared static have internal linkage.

External linkage

Elements referenced by identifiers with external linkage can be referenced from multiple scopes or translation units; such identifiers therefore always reference the same entities. Variables and functions with file scope that are declared extern have external linkage, unless already given internal linkage in the same translation unit.

Storage classes

Storage classes and scopes combine to determine linkages and variable lifetimes.

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

Namespace Local Class
(none) External External External
static Internal None External
extern External External
auto None
register None

For functions:

Namespace Class
(none) External External
static Internal External
extern External

static

Local variables declared static are initialized the first time they are encountered; in this they differ from other static variables, which lack deterministic initialization. Unlike other local variables, they are allocated on the heap, and they persist for the lifetime of the program. Their relative deinitialization order is undefined.

extern

Non-class functions have extern storage by default.

auto

Local variables have auto storage by default.

register

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

mutable

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

Qualifiers

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

const

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.

Only volatile functions can be invoked by instances or functions declared volatile. Functions declared volatile can modify non-volatile variables, however. Declaring a class type instance volatile causes its data to be considered volatile as well.

Vexing parse

Any statement that can be interpreted as a declaration must interpreted that way; this is known as the most vexing parse. For instance, if some 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();
  ...

However, 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 then be defined outside the block, but all namespace members must 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 an individual identifier into the current scope, allowing it to be referenced without specifying its namespaces:


using nStr::gUp;

A compilation error results if the specified 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 referenced.

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; the distinction is important for do while loops, as continue always causes 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 the definition of a variable 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; however, it is not safe to delete 'dangling' pointers, which reference already-deleted memory.

To ensure that the correct destructor is called, any pointer 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[] to ensure the destructor is 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 called when allocation fails.

Bitwise operators

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

Sign operators

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

Sequence operator

Both operands are evaluated by the sequence operator, and the result of the right one is returned:


int oYOrig = ++oX, ++oY;

This operator has lower precedence than any other.

Ternary operator

The result of the ternary operator 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);

Functions

Function declarations are also known as prototypes.

Overloading

Overloading allows the same name to be associated with different functions in the same scope. When an 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 before it looks outside that scope 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 the instance represented in the definition by this.

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

Overloading assignment operators

If the assignment operator is not overloaded for a given class type, ordinary memberwise assignment is performed. Overloaded assignment operators are not inherited by subclasses; an assignment overload in a base class is called when that part of the derived class is assigned, however. When overloading the assignment operator, it is advisable 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.

The size of the object being allocated is passed automatically to operator new. The size of the entire array is passed to operator new[]. The pointer being deallocated is passed to the delete operator:


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 is called. If such an overload is not defined, no delete is 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 is passed automatically:


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

Exceptions

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

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.

The first catch block to match a given exception is executed, even if a succeeding block matches more closely. If no block in the call stack matches the exception, terminate is called. By default, terminate ends the program, but a custom handler can be set by passing a callback to set_terminate.

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

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

Exception specifications

An exception specification limits the types allowed to 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 is 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 exception specification as was declared in the base class, or a more restrictive one.

If no type is listed in the specification, no exception is allowed to 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. Often this reiterates the parameter list, but in partial or explicit specialization, some or all parameters are replaced with specific types:


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

Template functions

Template functions are defined much as template classes are, but the template argument list is not 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 within classes that are not themselves template classes:


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

template <class 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 following the first default argument also require defaults. An argument list must be specified when specializing a template class, even if all defaults are accepted:


tPt<> oPt;

Type parameters

Type parameters are preceded by typename or class, these being equivalent in this context.

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 data is not technically 'passed' during specialization. Template arguments must be constant at compile-time.

Non-type parameters can be:

  • Integral types;
  • Enumerations;
  • References to variables with external linkage;
  • Pointers to variables or functions with external linkage;
  • Pointers to class variables or non-overloaded 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, and need share nothing, in fact, but their names and template parameter signatures.

To distinguish a given explicit specialization from others, the argument list is populated with specific types. Since all parameters are constrained by the argument list, an empty parameter list is specified in the class definition:


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 whatsoever is needed for class function specializations:


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

Partial specialization

Partial specialization allows distinct implementations to be defined 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

Within template definitions, typename explicitly marks identifiers as types:


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

Because the compiler knows nothing about a given parameter type until the template is specialized, it may not be able to determine whether a member of such a type is itself a type, or possibly something else. Visual Studio does not require typename in this situation, 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

If no value follows the identifier in a #define directive, the identifier is defined, but it is replaced with the empty string during preprocessing.

#undef cancels the effect of a #define directive.

Conditional directives

#ifdef and #ifndef test whether a preprocessor identifier is 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 eventually by #endif.

#include

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


#include <iostream>

Filenames in double quotes are typically located in the folder containing the file performing the include. Visual Studio then searches within folders containing files that include that file. Most implementations finally locate the file as though its name were enclosed in angle brackets:


#include "Test.h"

#error

Compilation is aborted if the preprocessor encounters #error. The string following #error, if any, is 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 the program text represented by a macro argument with double quotes:


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

##

The ## operator concatenates the strings represented by its macro arguments into a single string of program text:


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

Predefined macros

These macros are defined by default:

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

Miscellanea

The main function

main accepts zero or two arguments:


int main()

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. Instead of returning from main, programs can also be terminated with exit, which accepts a return value. If neither return nor exit is called, zero is returned.

Sources

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

The C++ Programming Reference, Third Edition
Bjarne Stroustrup
1998, 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