Standards
C++11
- see p. xix “New Features in C++11”
- list of all
N####
papers of the WG21 C++ Standards Committee: list- eg.
2022 N4903-N4927
indicates that the paper numbersN4903
toN4927
can be found in the listed mailings under year2022
- eg.
Definitions
Entity
From cppreference:
The entities of a C++ program are
- values,
- objects,
- references,
- structured bindings (since C++17),
- functions,
- enumerators,
- types,
- class members,
- templates,
- template specializations,
- parameter packs (since C++11), and
- namespaces.
Note: Preprocessor macros are not C++ entities.
Declaration
#include <type_traits>
struct S
{
int member;
// decl-specifier-seq is "int"
// declarator is "member"
} obj, *pObj(&obj);
// decl-specifier-seq is "struct S { int member; }"
// declarator "obj" declares an object of type S
// declarator "*pObj" declares a pointer to S,
// and initializer "(&obj)" initializes it
int i = 1, *p = nullptr, f(), (*pf)(double);
// decl-specifier-seq is "int"
// declarator "i" declares a variable of type int,
// and initializer "= 1" initializes it
// declarator "*p" declares a variable of type int*,
// and initializer "= nullptr" initializes it
// declarator "f()" declares (but doesn't define)
// a function taking no arguments and returning int
// declarator "(*pf)(double)" declares a pointer to function
// taking double and returning int
Details:
“A simple declaration is a statement that introduces, creates, and optionally initializes one or several identifiers, typically variables.”
decl-specifier-seq init-declarator-list(optional);
“decl-specifier-seq
is a sequence of the following whitespace-separated specifiers, in any order:”
- type specifier:
int
,double
, class specifier, cv qualifier, etc. typedef
inline
,virtual
,explicit
friend
constexpr
- storage class specifier:
register
,static
,thread_local
,extern
,mutable
- … etc.
“init-declarator-list
is a comma-separated sequence of one or more init-declarators,
which have the following syntax:”
declarator initializer(optional)
“Each declarator
(a part of an init-declarator-list
, see code block above) introduces exactly one object, reference, function, or (for typedef declarations) type alias, whose type is provided by decl-specifier-seq
and optionally modified by operators such as &
(reference to) or []
(array of) or ()
(function returning) in the declarator.”
Deep Copy vs Shallow Copy
see stackoverflow
Areas of Memory
- stack - Local variables, function parameters
- code space - Code
- global namespace - global variables
- registers - used for internal housekeeping functions, such as keeping track of the top of the stack and the instruction pointer
- free store = heap = dynamic memory - Just about all of the remaining memory
Value Categories
Just learn them by using this list of examples.
From learn.microsoft.com:
- Every C++ expression has a type, and belongs to a value category.
- The value categories are the basis for rules that compilers must follow when creating, copying, and moving temporary objects during expression evaluation.
- C++17:
- A glvalue is an expression whose evaluation determines the identity of an object, bit-field, or function.
- A prvalue is an expression whose evaluation initializes an object or a bit-field, or computes the value of the operand of an operator, as specified by the context in which it appears.
- has no address that is accessible by your program
- examples
- literals,
- function calls that return a nonreference type,
- eg.
str.substr(1, 2)
,str1 + str2
, orit++
- eg.
- temporary objects that are created during expression evaluation but accessible only by the compiler
&a
(address-of expression)a++
anda--
a + b
,a % b
,a & b
,a << b
, (built-in arithmetic expressions)
- An xvalue is a glvalue that denotes an object or bit-field whose resources can be reused (usually because it is near the end of its lifetime).
- has an address that no longer accessible by your program but can be used to initialize an rvalue reference, which provides access to the expression
- examples
- Certain kinds of expressions involving rvalue references (8.3.2) yield xvalues, such as a call to a function whose return type is an rvalue reference or a cast to an rvalue reference type.
- function calls that return an rvalue reference, and
- the array subscript expressions where the array is an rvalue reference
- member and pointer to member expressions where the object is an rvalue reference
- An lvalue is a glvalue that isn’t an xvalue.
- has an address that your program can access
- examples
- variable names, including
const
variables, - array elements,
- function calls that return an lvalue reference,
- bit-fields,
- unions, and
- class members
*p
(indirection expression)a[n]
(subscript expression)++a
and--a
a = b
,a += b
,a %= b
(assignment and compound assignment expression)
- variable names, including
- An rvalue is a prvalue or an xvalue.
- “The whole massacre began with the move semantics.”
lvalue and rvalue (informal)
- every expression in C++ is either an lvalue or an rvalue
- inherited from C
- in C: simple mnemonic purpose: lvalues could stand on the left-hand side of an assignment whereas rvalues could not
- in C++: Roughly speaking,
- when we use an object as an rvalue, we use the object’s value (its contents).
- when we use an object as an lvalue, we use the object’s identity (its location in memory).
- we can use an lvalue when an rvalue is required, but we cannot use an rvalue when an lvalue (i.e., a location) is required
POD Types (for Layout Dependent Operations)
phth:
- Some programs and compilers use operations that depend on a particular memory layout.
- Therefore, C++14 introduced three categories of simple
class
es andstruct
s,- trivial
- standard-layout
- POD
- By checking these categories programs can check the suitability of any given type for operations that depend on a particular memory layout.
Layout
- layout: refers to how the members of an object of
class
,struct
orunion
type are arranged in memory.- In some cases, the layout is well-defined by the language specification.
- But when a
class
orstruct
contains certain C++ language features such as virtual base classes, virtual functions, members with different access control, then the compiler is free to choose a layout.- That layout may vary depending on what optimizations are being performed and in many cases the object might not even occupy a contiguous area of memory.
- POD type: When a class or struct is both trivial and standard-layout, it is a POD (Plain Old Data) type.
- The memory layout of POD types is therefore contiguous and each member has a higher address than the member that was declared before it, so that byte for byte copies and binary I/O can be performed on these types.
- Scalar types such as
int
are also POD types. - POD types that are classes can have only POD types as non-
static
data members.
PODType (“Plain Old Data” Type)
- “This means the type is compatible with the types used in the C programming language, that is, can be exchanged with C libraries directly, in its binary form.” (cppreference)
- scalar types
- “object types that are not array types or class types”, → named requirements
- “The following types are collectively called scalar types:” (cppreference)
- arithmetic types
- enumeration types
- pointer types
- pointer-to-member types
std::nullptr_t
- cv-qualified versions of these types
- POD classes
- arrays of such types
- cv-qualified versions of these types
- scalar types
- in particular, if a type is a PODType, this means that the type is compatible with C
struct
s - details (including historic changes):
- informal:
Operators
- the scope operator “
::
” - left-shift operator “
<<
” (bitwise), cppreference- see example on stackoverflow
- the output operator “
<<
”- returns its left-hand operand
- right-shift operator “
>>
” (bitwise), cppreference- see example on stackoverflow
- the input operator “
>>
”- returns its left-hand operand
- assignment operators (
=
)- requires a (non
const
, ie. modifiable) lvalue as its left-hand operand - yields its left-hand operand as an lvalue
- sub-categories:
- copy assignment operator
- move assignment operator
- compound assignment operator (
+=
)
- requires a (non
- arithmetic operators
- addition operator (
+
) - shift operators “
<<
” and “>>
” (bitwise)
- addition operator (
- logical and relational operators
- logical AND (
&&
) - less than (
<
)
- logical AND (
- the call operator (
()
) - increment operator “
++
” and decrement operator “--
”- Pre-increment and pre-decrement operators
- increments or decrements the value of the object and
- returns a reference to the result.
- an lvalue expression that identifies the modified operand
- the expression
++x
is exactly equivalent tox += 1
- the expression
--x
is exactly equivalent tox -= 1
- Post-increment and post-decrement
- creates a copy of the object,
- increments or decrements the value of the object and
- returns the copy from before the increment or decrement.
- result is prvalue copy of the original value of the operand.
- the expression
x++
modifies the value of its operand as if by evaluatingx += 1
- the expression
x--
modifies the value of its operand as if by evaluatingx -= 1
- Pre-increment and pre-decrement operators
- complement operator (
~
) - member access operators
- subscript operator (
a[b]
)- “provides access to an object pointed-to by the pointer or array operand”
- dereference/indirection operator (
*a
)- “provides access to an object or function pointed-to by the pointer operand”
- converts a pointer value to an lvalue, (learn.microsoft.com)
- The operand of the indirection operator must be a pointer to a type, (learn.microsoft.com)
- result of the indirection expression is the type from which the pointer type is derived, (learn.microsoft.com)
- eg. if the operand is of type
char*
, the result of the expression is of typechar
- eg. if the operand is of type
- address-of operator (
&a
)- “creates a pointer pointing to the object or function operand”
- requires an lvalue operand
- returns a pointer to its operand as a prvalue
- result of the expression is a pointer type (an prvalue) derived from the type of the operand, (learn.microsoft.com)
- eg. if the operand is of type
char
, the result of the expression is of typechar*
(pointer tochar
)
- eg. if the operand is of type
- member of object/dot operator (
a.b
)- “provide access to a data member or member function of the object operand”
- returns the member named by the right-hand operand
- member of pointer (
a->b
, equivalent to(*a).b
)- “provide access to a data member or member function of the class pointed-to by the pointer operand”
- subscript operator (
Lvalues vs Rvalues and Operators
- Operators differ as to whether they
- require lvalue or rvalue operands
- return lvalues or rvalues
- Examples of operators that involve lvalues:
- Assignment
- requires a (non
const
) lvalue as its left-hand operand - yields its left-hand operand as an lvalue.
- requires a (non
- address-of operator
- requires an lvalue operand
- returns a pointer to its operand as an rvalue.
- built-in dereference and subscript operators and the iterator dereference and string and vector subscript operators
- all yield lvalues.
- built-in and iterator increment and decrement operators
- require lvalue operands
- the prefix versions (which are the ones used so far) also yield lvalues.
- Assignment
Comma Operator
cppreference:
The comma operator expressions have the form
E1 , E2
In a comma expression E1, E2
, the expression E1
is evaluated, its result is discarded (although if it has class type, it won’t be destroyed until the end of the containing full expression), and its side effects are completed before evaluation of the expression E2
begins.
The type, value, and value category of the result of the comma expression are exactly the type, value, and value category of the second operand, E2
.
Associativity:
From stackoverflow:
“the ,
operator in C++ is associative. It doesn’t matter which way you parenthesize the expressions. ((a, b), c)
and (a, (b, c))
both evaluate and discard a
, then evaluate and discard b
, then evaluate c
and that’s the result.”
Expressions, Literals
source: https://en.cppreference.com/w/cpp/language/expressions
- expression := sequence of operators and their operands, that specifies a computation.
- primary expression := literals, id-expressions, lambda-expressions, fold-expressions, requires-expressions
- literals := the tokens of a C++ program that represent constant values ( = Konstanten ) (dh alle Zeichen, die man “nicht ändern kann”)
- integer literals (eg
1
,2
,3
, …) - character literals (eg einzelne Buchstaben)
- boolean literals (
true
undfalse
) - nullptr (Merke:
nullptr
ist ein literal!)
- integer literals (eg
- literals := the tokens of a C++ program that represent constant values ( = Konstanten ) (dh alle Zeichen, die man “nicht ändern kann”)
- C++ expression properties: “each C++ expression is characterized by two independent properties”
- 1. type
- 2. value category: (see Value Categories)
- Primary categories:
- prvalue expression
- literals (s.o.)
- arithmetic expressions (
+
,-
,%
,…) - …
- xvalue expression
- lvalue expression
- prvalue expression
- Mixed categories:
- glvalue expression
- rvalue expression
- Primary categories:
- full-expression: “A full expression is an expression that is not part of another expression or of a declarator.”, source
Statements
- “fragments of the C program that are executed in sequence”
- 5 types
- 1) compound statements (aka block)
- “a brace-enclosed sequence of statements and declarations.”
- “Each compound statement introduces its own block scope.”
- 2) expression statements
- “An expression followed by a semicolon is a statement.”
- 3) selection statements
- 4) iteration statements
- 5) jump statements
- 1) compound statements (aka block)
cppreference:
Statements are fragments of the C++ program that are executed in sequence.
- The body of any function is a sequence of statements.
- For example:
int main()
{
int n = 1; // declaration statement
n = n + 1; // expression statement
std::cout << "n = " << n << '\n'; // expression statement
return 0; // return statement
}
C++ includes the following types of statements:
- labeled statements;
- expression statements;
- “An expression statement is an expression followed by a semicolon.”
- syntax:
attr(optional) expression(optional);
- “Most statements in a typical C++ program are expression statements, such as assignments or function calls.”
- “An expression statement without an expression is called a null statement. It is often used to provide an empty body to a for or while loop.”
- compound statements;
- selection statements;
- iteration statements;
- jump statements;
- declaration statements;
- try blocks;
- atomic and synchronized blocks (TM TS).
CV Type Qualifiers
- “Appear in any type specifier, including decl-specifier-seq of declaration grammar, to specify constness or volatility of the object being declared or of the type being named.”
const
- defines that the type is constant.volatile
- defines that the type is volatile.
As of 2014 and C11, there are four type qualifiers in standard C: const
(C89), volatile
(C89), restrict
(C99) and _Atomic
(C11) – the latter has a private name to avoid clashing with user-defined names. The first two of these, const
and volatile
, are also present in C++, and are the only type qualifiers in C++. Thus in C++ the term “cv-qualified type” (for const
and volatile
) is often used for “qualified type”, while the terms “c-qualified type” and “v-qualified type” are used when only one of the qualifiers is relevant.
Of these, const
is by far the best-known and most used, appearing in the C and C++ standard libraries and encountered in any significant use of these languages, which must satisfy const-correctness. The other qualifiers are used for low-level programming, and while widely used there, are rarely used by typical programmers. For a time however volatile
was used by some C++ programmers for synchronization during threading, though this was discouraged and is now broken in most compilers.
Factory
Wikipedia:
- In object-oriented programming, a factory is an object for creating other objects;
- formally, it is a function or method that returns objects of a varying prototype or class from some method call, which is assumed to be “new”.
- More broadly, a subroutine that returns a “new” object may be referred to as a “factory”, as in factory method or factory function. (“even if functions are not objects”)
- The factory pattern is the basis for a number of related software design patterns.
Invariants
BS4.3
- “invariant” aka “class invariant”
- “a statement of what is assumed to be true for a class”
- eg. for
Vector
, the statement “elem
points to an array ofsz
doubles” is an invariant- nothing
Vector
does makes any sense unless the members ofVector
have reasonable values
- nothing
- eg. for
- job of a constructor: establish the invariant for its class (so that the member functions can rely on it)
- job of the member functions: make sure that the invariant holds when the member functions exit
Parameterized Types
“C# Generics and C++ templates are both language features that provide support for parameterized types. However, there are many differences between the two.”, learn.microsoft.com
“templates provide compile-time parameterization”, learn.microsoft.com
Side Effects
cppreference:
Evaluation of Expressions
Evaluation of each expression includes:
- Value computations: calculation of the value that is returned by the expression. This may involve determination of the identity of the object (glvalue evaluation, e.g. if the expression returns a reference to some object) or reading the value previously assigned to an object (prvalue evaluation, e.g. if the expression returns a number, or some other value).
- Initiation of side effects: access (read or write) to an object designated by a volatile glvalue, modification (writing) to an object, calling a library I/O function, or calling a function that does any of those operations.
Header Files
Using C Headers in C++
- use the C++ versions of C library headers
- Headers in C have names of the form
name.h
. The C++ versions of these headers are namedcname
. - Warning: names defined in the
cname
headers are defined inside thestd
namespace, whereas those defined in the.h
versions are not
- Headers in C have names of the form
Newline at EOF
It is good style to always put a newline at the end of text files (POSIX Standard), stackoverflow
- this is part of language standards:
- in
C
: newline required at the end of header files - in
C++
: beforeC++11
: newline required at the end of header files - “No newline at end of file” compiler warning, stackoverflow
- in
- in
git
: stackoverflow
Include Syntax
Usual practice is to use the #include "local.h"
form for headers inside a library/package/module
, and the #include <external.h>
form for headers from external/3rd-party
or system libraries.
Lippman, Lajoie, Moo: Headers from the standard library are enclosed in angle brackets (< >
). Those that are not part of the library are enclosed in double quotes (" "
).
Keywords
- see Table 2.3
using
Using the namespace designation is good form because, if you use using namespace some_namespace
, you run the risk of inadvertently using objects from the wrong library. This takes some effort to fix, whereas using the namespace designation cannot lead to such conflicts (see example). I.e. from a maintenance perspective using the namespace designation is best practice.
= default
= default
can be used only on member functions that have a synthesized version- “force the automatic generation” (cppreference)
- defaulted functions:
- explicitly-defaulted function definition: as an explicit instruction to the compiler to generate a special member function
- to explicitly ask the compiler to generate the synthesized versions of the copy-control members
- the synthesized function is implicitly inline (just as is any other member function defined in the body of the class)
- if we do not want the synthesized member to be an inline function, we can specify
= default
on the member’s definition (outside the class)
- if we do not want the synthesized member to be an inline function, we can specify
= delete
- Motivation: how to prevent copies?
- simply not defining the copy-control members does not work because the compiler will synthesize them, if we do not define them
- deleted functions:
- one that is declared but may not be used in any other way
- signals to the compiler (and to readers of our code) that we are intentionally not defining these members
- A
=delete
makes an attempted use of the deleted function a compile-time error (BS6.1.1) - Unlike
= default
,= delete
- must appear on the first declaration of a deleted function
- we can specify
= delete
on any function- sometimes also useful to guide the function-matching process
- use cases:
- “A base class in a class hierarchy is the classic example where we don’t want to allow a memberwise copy” (BS6.1.1)
- Warning: You can
delete
a destructor. However, it is not possible to- define an object of a type with a deleted destructor
delete
a pointer to a dynamically allocated object of a type with a deleted destructor- ie you can dynamically allocate such objects, but you cannot free them
- “synthesized as deleted”
- in essence, if a class has a data member that cannot be default constructed, copied, assigned, or destroyed, then the corresponding copy-control member will be a deleted function
- prior to the new standard:
- prevent copying by declaring the copy constructor and copy-assignment operator as
private
- not best practice! Use
= default
instead.
- not best practice! Use
- prevent copying by declaring the copy constructor and copy-assignment operator as
const
- top-level const: indicates that an variable/object itself is
const
- low-level const: appears in the base type of compound types such as pointers or references
int i = 0;
int *const p1 = &i; // we can't change the value of p1; const is top-level
const int ci = 42; // we cannot change ci; const is top-level
const int *p2 = &ci; // we can change p2; const is low-level
const int *const p3 = p2; // right-most const is top-level, left-most is not
const int &r = ci; // const in reference types is always low-level
- When we copy an object top-level
const
s are ignored
i = ci; // ok: copying the value of ci; top-level const in ci is ignored
p2 = p3; // ok: pointed-to type matches; top-level const in p3 is ignored
- low-level
const
s are never ignored (ie. both objects must have the same low-levelconst
qualification)- In general, we can convert a non
const
toconst
but not the other way round
- In general, we can convert a non
int *p = p3; // error: p3 has a low-level const but p doesn't
p2 = p3; // ok: p2 has the same low-level const qualification as p3
p2 = &i; // ok: we can convert int* to const int*
// see "references.md" -> "reference to const"
int &r = ci; // error: can't bind an ordinary int& to a const int object
const int &r2 = i; // ok: can bind const int& to plain int
Guidelines
- you should not declare member variables as
const
, stackoverflowconst
member variables usually serve no purpose
- For a class type, if a piece of data is not supposed to change after being initialized, enforce this by design of the
public
API, not via theconst
key word., reddit const
fields are often not necessary - it is enough to pass aconst
object (or a pointer/reference to aconst
object), isocpp
Constant Expressions (C++11)
constant expressions:
- an expression
- whose value cannot change and
- that can be evaluated at compile time.
- examples:
- A literal
- A
const
object that is initialized from a constant expression
const int max_files = 20; // max_files is a constant expression
const int limit = max_files + 1; // limit is a constant expression
int staff_size = 27; // staff_size is not a constant expression
const int sz = get_size(); // sz is not a constant expression (because value of its initializer is not known until run time)
constexpr Declaration
- since C++11
- introduced because it can be difficult to determine that an initializer is a constant expression
- asks the compiler to verify that a variable is a constant expression
- Variables declared as
constexpr
are- implicitly
const
and - must be initialized by constant expressions
- implicitly
- cannot use an ordinary function as an initializer for a
constexpr
variable, but we can useconstexpr
functions - best practice:
- use
constexpr
for variables that you intend to use as constant expressions
- use
constexpr int mf = 20; // 20 is a constant expression
constexpr int limit = mf + 1; // mf + 1 is a constant expression
constexpr int sz = size(); // ok only if size is a constexpr function
Possible Types
- The types we can use in a
constexpr
are known as literal types- eg. arithmetic, reference, and pointer types
constexpr Pointer
- pointers and references as
constexpr
s must be initialized from- the
nullptr
literal - the literal (i.e., constant expression)
0
- an object that remains at a fixed address, eg.
- an object defined outside of any function (those defined inside are not stored at fixed addresses)
- local
static
objects/variables
- the
// the `constexpr` specifier applies to the pointer, not the type to which the pointer points:
const int *p = nullptr; // p is a pointer to a const int
constexpr int *q = nullptr; // q is a const pointer to int
// a constexpr pointer may point to a const or a nonconst type:
constexpr int *np = nullptr; // np is a constant pointer to int that is null
int j = 0;
constexpr int i = 42; // type of i is const int
// i and j must be defined outside any function
constexpr const int *p = &i; // p is a constant pointer to the const int i
constexpr int *p1 = &j; // p1 is a constant pointer to the int j
constexpr Functions
Note: these are the C++11 rules (the C++14 rules are in the next section)
- the new standard lets us define certain functions as
constexpr
- must be simple enough that the compiler can evaluate them at compile time
- can be used in the initializer of a
constexpr
variable - defined like any other function but must meet certain restrictions:
- return type and the type of each parameter must be a literal type
- function body must contain exactly one return statement
constexpr int new_sz() { return 42; }
constexpr int foo = new_sz(); // ok: foo is a constant expression
- compiler will replace a call to a
constexpr
function with its resulting value constexpr
functions are implicitlyinline
- function body may contain other statements so long as those statements generate no actions at runtime
- eg. null statements, type aliases, and using declarations
- is permitted to return a value that is not a constant:
// scale(cnt) is a constant expression if "cnt" is a constant expression
constexpr size_t scale(size_t cnt) { return new_sz() * cnt; }
int i = 2; // i is not a constant expression
int arr2[scale(i)]; // error: scale(i) is not a constant expression
int arr[scale(2)]; // ok: scale(2) is a constant expression
Constant Expressions (C++14)
- with C++14,
constexpr
functions can make use of most control structures available in general C++ code (eg.for
loops) - the compiler can evaluate a
constexpr
function at compile time, but it need not necessarily do so- in a context that requires a compile-time value (e.g., an array length or a nontype template argument), the compiler will attempt to evaluate a call to a
constexpr
function at compile time and issue an error if that is not possible (since a constant must be produced in the end) - in other contexts, the compiler may or may not attempt the evaluation at compile time but if such an evaluation fails, no error is issued and the call is left as a run-time call instead
- at block scope, the compiler can decide whether to compute it at compile or run time
- in a context that requires a compile-time value (e.g., an array length or a nontype template argument), the compiler will attempt to evaluate a call to a
// assume "isPrime" is a constexpr function
constexpr bool b1 = isPrime(9); // evaluated at compile time
const bool b2 = isPrime(9); // evaluated at compile time if in namespace scope
bool fiftySevenIsPrime() {
return isPrime(57); // evaluated at compile or running time
// ("at block scope, the compiler can decide whether
// to compute it at compile or run time")
}
int x;
...
std::cout << isPrime(x); // evaluated at run time
Type Aliases
- A type alias
- is a type name
- can appear wherever a type name can appear
typedef
- scope: Typedef names are only in effect in the scope where they are visible: different functions or class declarations may define identically-named types with different meaning. (cppreference)
- do not substitute: do not interpret a declaration that uses a type alias by conceptually “replacing” the alias with its corresponding type
// do not simply substitute this
typedef char *pstring;
const pstring cstr = 0; // - cstr is a constant pointer to char, ie. char *const cstr
// - base type is const char* (a pointer type)
const pstring *ps; // ps is a pointer to a constant pointer to char
// WRONG interpretation:
const char *cstr = 0; // wrong interpretation of const pstring cstr
// - declares cstr as a pointer to const char rather than as a const pointer to char
// - the base type is char (without asterisk!)
// - the asterisk is part of the declarator!
using
- C++11
- aka alias declaration
using SI = Sales_item; // SI is a synonym for Sales_item
Types
List of Types
cppreference: list
Lippman:
- primitive types (aka in Lippman: “built-in types”, in cppreference: “fundamental types”)
- arithmetic types
- integral types (
char
,int
,bool
) - floating-point types (
float
,double
)
- integral types (
void
- arithmetic types
- non-primitive types
- class types
- library types (e.g.
string
,istream
,ostream
)
- library types (e.g.
- compound types (2.3 “a type that is defined in terms of another type”)
- pointer types
- reference types (2.3.1)
- array types
- container types (9)
- sequential (full list, see table 9.1) (“element order corresponds to the position at which elements are put into the container”)
string
(see “C Notes”)array
vector
- linked lists
list
(doubly linked)forward_list
(singly linked)
- associative (“store elements based on the value of a key”)
- unordered
- sequential (full list, see table 9.1) (“element order corresponds to the position at which elements are put into the container”)
- class types
Print Type
typeid(a).name()
- Problem with this approach: see stackoverflow
- throws away cv-qualifiers (ie.
const
,volatile
), references, and lvalue/rvalue-ness (because the standard mandates this behavior)
- throws away cv-qualifiers (ie.
Incomplete Types
- after a declaration and before a definition is seen, a type is an incomplete type
Forward Declarations
// forward declaration:
// declare a class without defining it
class Screen; // declaration of the Screen class
- We can
- define pointers or references to such types
- declare (but not define) functions that use an incomplete type as a parameter or return type
- we cannot
- create objects of that type
- use a reference or pointer to access a member of the type
Examples:
- data members of the class’ own type:
- Because a class is not defined until its class body is complete, a class cannot have data members of its own type
- However, a class is considered declared (but not yet defined) as soon as its class name has been seen. Therefore, a class can have data members that are pointers or references to its own type
Cast / Typecast (explicit conversion)
Lip, p165
- general rule: avoid casts!
reinterpret_casts
:- Such casts are always hazardous
const_cast
:- can be useful in the context of overloaded functions
- Other uses of
const_cast
often indicate a design flaw
static_cast
anddynamic_cast
:- should be needed infrequently
- every time you write a cast, you should think hard about whether you can achieve the same result in a different way
- if the cast is unavoidable, errors can be mitigated by limiting the scope in which the cast value is used and by documenting all assumptions about the types involved
Named Casts
static_cast
,dynamic_cast
,const_cast
, andreinterpret_cast
const_cast
- changes only a low-level
const
in its operand - “casts away the
const
” - using a
const_cast
in order to write to aconst
object is undefined
static_cast
- any well-defined type conversion, other than those involving low-level
const
, can be requested using astatic_cast
- useful for:
- assigning a larger arithmetic type to a smaller type
static_cast
to inform both the reader of the program and the compiler that we are aware of and are not concerned about the potential loss of precision- when we do an explicit cast, the compiler warning message is turned off
- to retrieve a pointer value that was stored in a
void
* pointer- warning: must be certain that the type to which we cast the pointer is the actual type of that pointer; if the types do not match, the result is undefined
- assigning a larger arithmetic type to a smaller type
void* p = &d; // ok: address of any nonconst object can be stored in a void*
// ok: converts void* back to the original pointer type
// pointer value is preserved: ie. result of the cast will be equal to the original address value "&d"
double *dp = static_cast<double*>(p);
cppreference:
3) If there is an implicit conversion sequence from expression
to target-type
, or if overload resolution for a direct initialization of an object or reference of type target-type
from expression would find at least one viable function, then static_cast<target-type>(expression)
returns the imaginary variable Temp
initialized as if by target-type Temp(expression);
, which may involve implicit conversions, a call to the constructor of target-type
or a call to a user-defined conversion operator.
Cast to void
// - https://stackoverflow.com/a/61205643/12282296
// - see "variadic_templates_pack_expansion.cpp"
// - note: phth modified the original code
// - remove phth's changes and "static_cast<void>(temp);" to see the warning
int temp[] = { (s += toString(args),0)... };
static_cast<void>(temp);
- Casting a variable expression to
void
to suppress this (“unused local variables or function parameters
”) warning has become an idiom in the C and later C++ community, stackoverflow
C-Style Casts
type(expr); // function-style cast notation
(type)expr; // C-language-style cast notation
- where a
static_cast
or aconst_cast
would be legal, the old-style cast does the same conversion as the respective named cast - if neither cast is legal, then an old-style cast performs a
reinterpret_cast
cppreference:
When the C-style cast expression is encountered, the compiler attempts to interpret it as the following cast expressions, in this order:
- a)
const_cast<target-type>(expression)
; - b)
static_cast<target-type>(expression)
, (…) - c)
static_cast
(with extensions) followed byconst_cast
; - d)
reinterpret_cast<target-type>(expression)
; - e)
reinterpret_cast
followed byconst_cast
.
The first choice that satisfies the requirements of the respective cast operator is selected, even if it cannot be compiled (see example). If the cast can be interpreted in more than one way as static_cast
followed by a const_cast
, it cannot be compiled.
Conditional Statements
switch Statement
- the
break
anddefault
keywords are optionalbreak
breaks out of the switch blockdefault
specifies some code to run if there is no case match
- Fallthrough: “Fall through is a type of error that occurs in various programming languages like C, C++, Java, Dart …etc. It occurs in
switch
-case
statements where when we forget to add abreak
statement and in that case flow of control jumps to the next line. Due to this, when any case is matched with the specified value, then control falls through to subsequent cases until abreak
statement is found.”, geeksforgeeks- related: learncpp
- related: cppreference
switch(expression) {
case x:
// code block
break;
case y:
// code block
break;
default:
// code block
}
Example
// https://www.w3schools.com/cpp/cpp_switch.asp
int day = 4;
switch (day) {
case 6:
cout << "Today is Saturday";
break;
case 7:
cout << "Today is Sunday";
break;
default:
cout << "Looking forward to the Weekend";
}
// Outputs "Looking forward to the Weekend"
argc, argv
see stackoverflow:
argv
and argc
are how command line arguments are passed to main()
in C and C++.
argc
will be the number of strings pointed to by argv
. This will (in practice) be 1 plus the number of arguments, as virtually all implementations will prepend the name of the program to the array.
The variables are named argc
(argument count) and argv
(argument vector) by convention, but they can be given any valid identifier: int main(int num_args, char** arg_strings)
is equally valid.
They can also be omitted entirely, yielding int main()
, if you do not intend to process command line arguments.
Try the following program:
#include <iostream>
int main(int argc, char** argv) {
std::cout << "Have " << argc << " arguments:" << std::endl;
for (int i = 0; i < argc; ++i) {
std::cout << argv[i] << std::endl;
}
}
Running it with ./test a1 b2 c3
will output
Have 4 arguments:
./test
a1
b2
c3
Debugging
see “C notes”
tools:
- gdb
- valgrind