Skip to content

Learn c++

This is an excerpt for : visit

chapter 0

chapter 0 was done without any comments needed.

chapter 1

initialization

3 forms of Initialization :

  • int a = 11.1; // (copy) only non-explicit constructors are called
  • int a(11.1); // (direct) any constructor is called if it matches
  • int a{11.1}; // (uniform) this is checked, and this one will fail !

non-explicit means all constructors without the 'explicit' keyword see chapter 9 !!

The uniform initialization sees that this would be a narrowing conversion and fail. The others perform default conversions and make a=11.

Important

favor uniform (brace) initialization when possible. Beware it is checked and thus the slowest of the three.

Note that brace initialization can handle more complex structures as well.

initialization
1
2
3
4
5
struct {
    int a;
    struct { char b; };
    float c;
} X = { 1, { 'a' }, 12.2 };

Adding a '=' does not make it copy assignment !

brace initialization
int x{11.1};
int z = {11.1}; // still brace initialization, it also fails

zero initialization

zero initialization
int x{}  // initialize to 0 or empty, the default zero value for the type
int y; // this is actually just uniform initialization with value 0

Important

Since C++11 and the introduction of move semantics, we now talk about copy construction and copy assignment opposite move construction and move assignment. (chapter 9)

multiple initialization

When using pointers, you need to put an * on all variables :

multiple on one line
int* x,y;   // x is a pointer to int, y is an int
(int *)x,y; // nice try... ERROR !
int *a, *b; // this is more clear, but ....

int *c;     // but this is just better :
int *d;     // one line with a description of the variable

const int ef;  // note that these ARE both const, f will fail !

// this is allowed as expected, but don't do it.
int x=1, y(2), z;

Important

Just always put each declaration on one line, initialize it and comment what it is used for.

uninitialized variables

Global and static variables are initialized to 0 because it can be done at compile time without overhead to the running program. Other variables would impose an overhead and so contain whatever is in the assigned memory at that time.

Warning

Some compilers DO initialize variables when running in debug mode ! NOT always with 0 by the way !!, again a reason to always initialize !

Important

Use -Wuninitialized (or -Wall) to make the compiler detect it.

identifiers

Important

best practice is naming variable in lowercase, and functions in either dromedaryCase() or with_under_score()

The compiler reserves names starting with underscores

operators

Did you know ? That the number of operands of an operator is called it's 'arity' : un-ary, bin-ary', tern-ary'.

expressions

Important

expressions always evaluate to 1 value, and expressions are always part of a statement.

If an expression is followed by only a semicolon, it becomes an 'expression statement'

expression statement
y = x = 5; // expression statement

It is true that the value of an expression can be used in another expression, but the value of the complete 'expression statement' is discarded ! So above y becomes 5, but the remaining 5 is discarded.

chapter 2

visit

functions

This is valid code :

functions
1
2
3
4
5
6
7
#include <iostream>

void f() { std::cout << "Hellon"; }

int main() {
    f;
}

It prints nothing, f is just evaluated as the pointer to f() and discarded.

Important

C++ does not have nested functions.

Also functions evaluate to expressions so this is valid:

functions evaluate to expressions
int num { getValueFromUser() }; // initialize num

Important

In c++ the second void in "void x(void)" should be omitted. It is still allowed but better is void x().

argument evaluation

The order of evaluation in C++ is NOT defined !!

argument evaluation order
#include <iostream>

int f(int y,int z) { printf("%d %dn", y, z); return y+z;}
int a() { printf("An"); return 20; }
int b() { printf("Bn"); return 30; }

int main() {
    f(a(),b());
    f(b(),a());
}   

This prints :

output
1
2
3
4
5
6
B
A
20 30
A
B
30 20

This seems to prove that g++ evaluates from right to left, judging by the order of prints.

However this program proves that g++ handles different orders even within the same program :

do not count on ordering
#include <iostream>
int f(int y,int z) { printf("%d %dn", y, z); return y+z; }


int main() {
    int y = 10;

    int x = f(y++,y);                # 10 11
    std::cout << x << std::endl;     # 21
    std::cout << y << std::endl;     # 11

    y = 10;
    x = f(y,y++);                    # 11 10
    std::cout << x << std::endl;     # 21
    std::cout << y << std::endl;     # 11

    return 0;
}

How can you explain this apart from this :

  • in the first call y++ is evaluated first, it pass 10 and then increments
  • the y is then evaluated and is passed as 11 # 10 11 return 21
  • in the second call y is not the first one to be evaluated !!, it should have been 10 !!, the only way to get these values is if y++ is again first evaluated !!

So x = f(y++,y); is left to right !! and x = f(y,y++); is right to left !!

This comes from visit.

Important

The compiler can evaluate operands and other sub expressions in any order, and may choose another order when the same expression is evaluated again.

Warning

never rely on arguments being evaluated in a particular order.

local variables

Local variables are destroyed in opposite order of creation when they leave scope.

scope and lifetime

Important

scope is a compiletime property, lifetime is a runtime property.

A small example of where scope is different from lifetime :

void f()
{
    static int x;
    x++;
}

f();
f();

x's lifetime is from line 1-8, x's scope is line 3-4. Another example is a function call: a local variable is out of scope during it but it's lifetime remains intact after the function returns.

declarations

They are properly called "forward declarations".

When you declare a function but provide no definition, it can still compile if the function was never called. It is like including a header file, the functions you never call are never resolved by the linker.

declarations
int not_there();    
int not_there();    // also note that you can have as
int not_there();    // much of the (exact) same declarations as you wish.

extern int x;
extern int x;       // also ok, it's declaration

static int y;
static int y;       // ERROR: static is a definition !!

main() { 
    // compiles fine unles you uncomment next line
    // not_there();
} 

one definition rule

Important

mainly : any function, object, type or template can only have one definition.

However it is more subtle than that, so there are 3 rules.

  • within a file a function,object,type or template can only have 1 definition
  • within a program a normal function or object can only have 1 definition
  • within a program types, templates and inline functions can have multiple definitions but they have to be the same !

Important

all definitions also serve as declarations. So you actually only need forward declarations.

Important

forward declaration that are NOT definitions are also called 'pure declarations'

namespaces

The general format for defining a namespace is

namespaces
1
2
3
4
5
6
namespace MyNameSpace {
    int uniquename;
    namespace nested {
        int uniquename;
    } 
}

And calling those names :

namespaces
MyNameSpace::uniquename;
MyNameSpace::nested::uniquename;

You can also directly use the nested format so this is all legal :

nesting
#include <iostream>
namespace a
{
    int var = 11;
    namespace a {
        int var = 12;
    }
}

namespace a::a::a {
    int var = 13;
}

int main()
{
    std::cout << a::var << 'n';
    std::cout << a::a::var << 'n';
    std::cout << a::a::a::var << 'n';
    return 0;
}

It will print as expected :

output
1
2
3
11
12
13

Important

::name defaults to using the global namespace, so name is ::name.

Important

You can use namespaces multiple times, they just get combined into one.

But of course you cannot use the same name within those separate blocks.

using

I usually avoid using 'using' ;) but if used note that it obeys block scope and you don't override a previous using but they add up.

Important

also you cannot un-use a namespace !

using
#include <iostream>
namespace a
{
    int x(10);
}

namespace b
{
    int x(20);
}

int main()
{
    using namespace a; 
    using namespace b;
    std::cout << x << 'n'; // problem ! both a::x and b::x are visible
    return 0;
}

If you include a complete namespace there will be more conflicts:

conflicts
#include <iostream> // imports the declaration of std::cout
int cout() // declares our own "cout" function
{
    return 5;
}

int main()
{
    using namespace std; // makes std::cout accessible as "cout"
    cout << "Hello, world!"; // this one is ambiguous 

    using std::cout;
    cout << "Hello, world!"; // this one is not !!

    return 0;
}

Try to favor using declarations (using x::y). They are more fine grained

Important

Try to avoid using directives (using namespace X).

Important

Try to avoid using declarations (using X::Y) in the global scope.

preprocessor

One note: the tutorial is WRONG about 'object-like macro's don't affect preprocessor directives. :

preprocessor
1
2
3
4
5
6
#define FOO 0 // 
#if FOO // this WILL become #if 0
    // so this DOES NOT get printed
    std::cout << FOO; // this WOULD become std::cout << 0, 
    // but it won't get generated bacause of #if FOO
#endif

This program prints nothing, if you would #define FOO 9 it would print 9 !

header files

c++ system header files do not have a .h extension because to differentiate them from the original files that did not use the std namespace.

Important

files without .h use the std namespace. Files beginning with c (cstdio, cstdlib) are still useful C functions.

include order

cpplint says :

  • system C++ first
  • system C second
  • all the rest last

Normally you should include all headers a source file needs in that sourcefile, but a put all headers in the same file to be able to use precompiled headers.

Here is a quick test te see if we can mix this...

include headers
1
2
3
4
5
6
# actually alias make -j 8 was disabled here :
make clean cleanheaders
time make # real    2m13.627s
make headers
make clean
time make # real    1m5.122s

This is about half the time. Now i disabled backend.hpp and restored all includes. The time in this setup becomes :

parallel compile
time make # real    1m2.161s
time make -j 8 #   real    0m13.611s  ;)

Actually faster so the best approach is to just 'gather' all used includes in one main header file, precompile that header and include it before any other header. It is just as fast and the includes form a backup and documentation. Also watch the parallel compile, it is 4 times quicker !

header guards

Important

Header guards make sure a header file is not included more than once. The file is still included in each header file (once).

Why not just avoid definitions in header files !? Because we need types to be accessible from multiple source files.

Important

Header guards are there mainly for class and struct definitions !! Also for enums,etc.

chapter 3 debugging

Ye... nothing new .

chapter 4 types

Important

std::nullptr_t is a fundamental data type since C++11

Also since C++11 char types come in different sizes : char, wchar_t, char8_t (C++20) char16_t and char32_t.

int16_t etc are NOT fundamental types.

integers

Officially you can declare a long as :

long int
signed long int x;

But it is advised to drop both the signed and int because they are defaults.

Important

Signed integer overflow will result in undefined behavior.

So you cannot expect that assigning or adding resulting in overflow will end up in a certain number! This is probably because compilers may choose where to put the sign bit in integers (1's/2's complement).

unsigned integers

When you assign 280 to an unsigned char it is NOT overflow but out of range or wrap around. This results in truncation.

Important

Out of range values (unsigned int) do behave predictably, the excess bits are just lost !

Important

google thinks unsigned integers should be avoided.

The reasons are somewhat cloudy, but mainly in subtraction you don't get negative numbers but huge numbers. On the other hand i don't see much advantage in using unsigned either. In any case do not mix signed and unsigned.

Important

in the standard library some containers use an unsigned integer as loop iterator ! Watch out when using auto.

integer sizes

Important

The fixed size int8_t, int16_t etc may not be defined on all architectures.

There is a set of integers that can be used to get the fastest and smallest integer that has at least x bits. int_fast8_t, int_fast16_t, int_fast32_t, and int_least8_t, int_least16_t, int_least32_t.

For instance you could ask for int_least16_t and get a 32 bit integer if the system has no 16 bit ints.

Best seems to just test your production system for the best int to use. A simple program for this :

sizes
#include <iostream>
#include <cstdint>

int main()
{
    std::cout << "std int : " << sizeof(int) * 8 << " bitsn";
    std::cout << "std long : " << sizeof(long) * 8 << " bitsn";

    std::cout << "fast 8: " << sizeof(std::int_fast8_t) * 8 << " bitsn";
    std::cout << "fast 16: " << sizeof(std::int_fast16_t) * 8 << " bitsn";
    std::cout << "fast 32: " << sizeof(std::int_fast32_t) * 8 << " bitsn";

    std::cout << "least 8: " << sizeof(std::int_least8_t) * 8 << " bitsn";
    std::cout << "least 16: " << sizeof(std::int_least16_t) * 8 << " bitsn";
    std::cout << "least 32: " << sizeof(std::int_least32_t) * 8 << " bitsn";

    return 0
} 

On linux this gave me :

output
1
2
3
4
5
6
7
8
std int : 32 bits
std long : 64 bits
fast 8: 8 bits
fast 16: 64 bits
fast 32: 64 bits
least 8: 8 bits
least 16: 16 bits
least 32: 32 bits

So you see even 2 byte ints are better represented by long, so in this case i used long integers.

Since i thing int_fast32_t is just a bit to long(12 chars) in code i fixed this by using this typedef, since it seems int_t is not defined yet.

fast integer
typedef int_t int_32fast_t;

Important

In contrast to the int_xx_t types, the fast and least types are guaranteed in c++11.

small integers

Due to an oversight in the c++ specs the small integer types std::int8_t and std::uint8_t sometimes are treated as char values :

small integers
#include <cstdint>
#include <iostream>

int main()
{
    std::int8_t myint = 65;
    std::cout << myint;

    return 0;
}

You would expect this to print 65, but it prints 'A' instead.

Important

Don't use the small int versions, just go for char directly then you know what to expect.

size_t

This is simply the type to use when handling sizes ( size_t sizeof(type); ). It is guaranteed to be unsigned and at least 16 bits.

floating point notation

Also scientific notation. The main format is

Important

The general form is "significand x 10exponent" in C++ this becomes sEe

By convention we always write 1 digit before the decimal and the rest after.

So the mass of the earth is 5,973,600,000,000,000,000,000,000 Kg

Written as \(5.973x10^{24}\)

This will be written like this in C++ 5.973000e+24 (6 digits) if you print it with %e. std::cout has a default of 5 digits :

float
#include <iostream>
#include <iomanip>
#include <stdio.h>

int main()
{
    double x = 5973652739000000000000000.0;

    printf("%lfn", x);
    printf("%en", x);

    std::cout << x << std::endl;
    std::cout << std::setprecision(10) << x << std::endl;
    return 0;
} 

Output :

result
1
2
3
4
5973652738999999885475840.000000
5.973653e+24
5.97365e+24
5.973652739e+24

So note that the number of digits is default and does hide some significant digits (this 6 significant digits is compiler specific !) setprecision from iomanip can be used to alter that.

Important

std::cout will not print the decimal part if it is 0. So 5.0 becomes 5 !!

sizes

name min size typical
float 4 4
double 8 8
long double 8 8,12,16

Important

The default for floating point literals is double, use f to specify float literals.

Important

Comparing floats means comparing bit by bit. This means precision mismatches will very easily go wrong.

rounding errors

Note that seemingly precise numbers (to a human) are not always precise in floats.

rounding double
#include <iostream>
#include <iomanip> // for std::setprecision()

int main()
{
    double c{1.0/3.0};
    double d{0.1};
    std::cout << d << 'n'; // use default cout precision of 6
    # 0.1
    std::cout << std::setprecision(17);
    std::cout << d << 'n';
    # 0.10000000000000001
    return 0;
}

We expect c to be a rounding problem but d can also not be exactly represented in 16 bits !!

Warning

Never use floating point numbers for financial or currency calculations. Use gmp, boost/multiprecision or some other arbitrary precision library.

Nan and Inf

These are different for different compilers, for instance this program :

Not a Number of Infinite
#include <iostream>

int main()
{
    double zero {0.0};
    double posinf { 5.0 / zero }; // positive infinity
    std::cout << posinf << std::endl;

    double neginf { -5.0 / zero }; // negative infinity
    std::cout << neginf << std::endl;

    double nan { zero / zero }; // not a number (mathematically invalid)
    std::cout << nan << std::endl;

    return 0;
}

Prints this on MSVC :

microsoft
1
2
3
1.#INF
-1.#INF
1.#IND

And for g++ :

gnome
1
2
3
inf
-inf
-nan

Inf stands for infinite, Nan for not-a-number and IND for indeterminate. You can't compare this with a literal, but you can use functions like std::isnan() from cmath. There is also a std::mininf() but this :

Important

Returns whether x is an infinity value (either positive infinity or negative infinity).

You can compare it to 0 however to get which one it is :

testing
1
2
3
4
5
6
7
8
double zero {0.0};
double posinf { 5.0 / zero }; // positive infinity
double neginf { -5.0 / zero }; // negative infinity

std::cout << std::isinf(posinf) << std::endl;  # 1
std::cout << std::isinf(neginf) << std::endl;  # 1
std::cout << (neginf < 0) << std::endl;  # 1
std::cout << (posinf < 0) << std::endl;  # 0

boolean

As in C booleans just contain an integer 0 or 1 for false and true. You can print the symbolic version like this:

booleans
#include <iostream>

int main()
{
    std::cout << true << std::endl;
    std::cout << false << std::endl;

    std::cout << std::boolalpha; // print bools as true or false

    std::cout << true << std::endl;
    std::cout << false << std::endl;
    return 0;
}

output

output
1
2
3
4
1
0
true
false

Just like other ints uniform initialization checks for narrowing conversions so :

initialization
bool x { 4 }; // fail
bool y = 4;   // works, y becomes 1/true

char

Important

char is by default signed, so the ascii charset contains characters up to value 127. All characters below 20 (space) are unprintable, also the negative ones.

wchar_t

This is mainly a windows/msvc type and it's size is implementation defined. So do not use this type. When you need to use unicode better use char16_t and char32_t because these sizes are defined hard.

literals

In C++14 the binary format was introduced, in C+11 these did not exist yet:

literals
#include <iostream>

int main()
{
    int bin{};
    bin = 0b1;  // assign binary 0000 0001 to the variable
    bin = 0b11; // assign binary 0000 0011 to the variable
    bin = 0b1010; // assign binary 0000 1010 to the variable
    bin = 0b11110000; // assign binary 1111 0000 to the variable

    return 0;
}

The separator is also only C++14 and up :

1000 separator
1
2
3
4
5
6
7
8
9
#include <iostream>

int main()
{
    int bin{ 0b1011'0010 };  // assign binary 1011 0010 to the variable
    long value{ 2'132'673'462 }; // much easier to read than 2132673462

    return 0;
}

number formatting

If you want to change format, you can use these constructs from iostream :

formatting
#include <iostream>

int main()
{
    int x { 12 };
    std::cout << x << 'n'; // decimal (by default)
    std::cout << std::hex << x << 'n'; // hexadecimal
    std::cout << x << 'n'; // now hexadecimal
    std::cout << std::oct << x << 'n'; // octal
    std::cout << std::dec << x << 'n'; // return to decimal
    std::cout << x << 'n'; // decimal

    return 0;
}

But strangely std::bin was not added. You can however print these using bitsets. Also an example of not using a type but a quantity:<8> as template parameter.

quantity template
#include <iostream>
#include <bitset> // for std::bitset

int main()
{
    // std::bitset<8> means we want to store 8 bits
    std::bitset<8> bin1{ 0b1100'0101 }; // binary literal for binary 1100 0101
    std::bitset<8> bin2{ 0xC5 }; // hexadecimal literal for binary 1100 0101

    std::cout << bin1 << ' ' << bin2 << 'n';
    std::cout << std::bitset<4>{ 0b1010 } << 'n'; // we can also print from std::bitset directly

    return 0;
}

magic numbers

Magic numbers are plain numbers without context in code.

magic number
int maxstudents { numClassrooms * 30 };

Here we have no idea where this 30 came from, probably it is 30 student per class but it is so much clearer if you do this:

clearer
1
2
3
constexpr int numClassrooms { 5 };
constexpr int studentperclass { 30 };
constexpr int maxstudents { numClassrooms * studentperclass };

Note that all three can be evaluated at compile time so they can (should) all be a constexpr.

Important

use constexpr for const values known at compiletime and const for dynamic const values.

const and constexpr

you can add const before or after the type, but starting with const is preferred

constexpr
const double gravity { 9.8 }; // preferred use of const before type
int const sidesInSquare { 4 }; // okay, but not preferred

symbolic constants

We got two flavours :

  • define CONST1 11

  • constexpr const1 11

The second one is preferred, with 3 good reasons :

  • macro's don't show up as values in the debugger.
  • macro's are replaced in the code prior to compilation. This could mess with the code in hard to sport ways.
  • macro's do not follow scoping rules. They do follow file scope when defined inside a source file.

That last one might actually be an advantage because it is very predictable.

chapter 5 operators

integer division

Before C++11 negative integer division could round either way, but that is fixed since then.

Important

Integer division drops the fraction so -7/4 is -1 (it is not rounded down but towards 0)

If you really want the fraction in an integer division you need to static_cast at least one of the operand to float, because the type of an expression is that of the widest type.

The remainder is also made predictable in c++11 :

Important

The remainder of x%y always has the sign of x, and also works towards 0

modulo
#include <iostream>
#include <bitset> // for std::bitset

int main()
{
    std::cout << 10% 6 << std::endl;    # 4
    std::cout << 10%-6 << std::endl;    # 4
    std::cout << -10% 6 << std::endl;   # -4
    std::cout << -10% -6 << std::endl;  # -4
    return 0;
}

increment decrement

++ in front of a variable increases the variable and returns the result. Technically this can't be done with the postfix versions.

Important

x++ means a copy of x is made, then x is incremented, and the copy is evaluated and becomes the value of the postfix increment expression.

This also means that prefix operators are better performant than postfix ones.

Important

Since the prefix version is simpler AND more performant, try to only use that.

This also means that prefix operators are better performant than postfix ones.

operator operands

Though all operators have a well defined order of evaluation that does not mean two operand are evaluated in a defined order.

operands
1
2
3
4
int a = 0;
int x = a++ * ++a * b++;
// is the same as x = (a++ * ++a) * b++;
// but it is NOT defined if a++ or ++a is evaluated first !!

It could be (10 * 12) * 10 or (11 * 11) * 10.

If left is evaluated first a++ is 10 at evaluation, and 11 afterwards. ++a is then incremented first : 12. b is always 10. If right is evaluated first, ++a is 11 at evaluation, if then a++ is evaluated it first evaluates to 11 and then increases to 12.

Warning

In general do not use side effects on the same variable in either function calls or expression. The result is undefined !!

comma operator

The comma operator is the lowest in the list, even lower than assignment. That is why you almost always encounter in between parentheses.

comma
int x = 1,2;    // means (x=1),(2)  // x = 1;
int y = (1,2);  // means y = (1,2); // y = 2;

The tutorial says the for loop statement is also a comma operator list, but i doubt that since the first (initializer) statement is only executed once.

Todo

can you find a definitive answer to that !?

Important

The parameter list of a function is NOT a comma separator list as in operators !!

division by zero

Note that 1/0 is not guaranteed to crash your computer, for instance on gcc :

/0
1/0;    // this is probably optimized out, it is never reached.
int x = 1/0;    // floating exception, will stop in gdb

Both times the compiler will complain but compile reluctantly.

conditional operator

The ?: operator has very low priority, almost anything apart from assignment wins so best use parentheses a lot.

However :

conditional
int x=10;
int y=20;

std::cout << (x < y) ? x : y;
// evaluates as :
(std::cout << (x < y)) ? x : y;     // it prints 1 
std::cout << ((x > y) ? x : y);     // prints 10

// this would be pointless: 
std::cout << ((x > y) ? (x) : (y)); // prints 10
// and this won't even compile :
std::cout << x<y ? x : y ;

Important

Note that the operands after ? and : evaluate as if they were parenthesized, so they can be left alone.

Warning

always use this format ( (x<y) ? a : b)

conditional const initialization

I used this myself lately, you can not assign a const in an if statement because of the scope:

const initialization
1
2
3
4
if (a<b) 
    const int myconst = a;
else
    const int myconst = b;

myconst will expire in the if statement, but this will work:

this works
const int my_const = (x<y) ? x : y;

Note that at first i made a typo which marks another detail about conditionals. They are expressions, so this missing = becomes a direct initialization :

error
1
2
3
4
5
int x=10;
int y=20;

const int my_const ((x<y) ? x : y);
// same as : const int my_const(10);

floating comparison

Warning

Never compare floating point values on equality.

If it is needed you want a 'close enough' comparison. Note that it is best to devise a function that compares within a percentage of the largest of the two values. These are called epsilon (small) values or functions. And you need the largest because that number will be the one that has the biggest 'fault'.

float compare
1
2
3
4
5
6
7
#include <cmath> // for fabs()

// return true if the difference between a and b is within epsilon percent of the larger of a and b
bool approximatelyEqual(double a, double b, double epsilon)
{
    return fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}

Still this may fail if numbers become really small (approach 0.0) you may also need to add an absolute check as well for the small numbers.

logical operators

There is no logical XOR in C++ :

no logical xor
1
2
3
4
5
6
7
8
9
int a=11, b=12;
bool aa=true, bb=false;

std::cout << (a & b) << 'n';
std::cout << (aa && bb) << 'n';
std::cout << (a | b) << 'n';
std::cout << (aa || bb) << 'n';
std::cout << (a ^ b) << 'n';
std::cout << (aa ^^ bb) << 'n';  // error !

But if you use booleans you can mimic the XOR with !=

mimic xor
1
2
3
4
5
6
bool a=1, b=1, c=0;

std::cout << (a!=b) << std::endl; // 1 ^ 1 = 0
// or even : 
std::cout << (a!=b!=c) << std::endl; // 1 ^ 1 ^ 0 = 0
// left to right makes (1!=1)!0 -> 0!=0 -> 0

chapter O bit manipulation

std::bitset ...........

C++ has the bitset type for expressing bits like bitfields in C :

bitsets
1
2
3
4
5
6
7
8
9
#include <bitset>

std::bitset<8> bs = { 0b1100'1100 };

cout << sizeof(myset) << endl;
cout << myset << endl;
// prints
8
11001100

Note that sizeof(myset) is 8 BYTES, don't be fooled by thinking it is 8 bits. This is just the minimum size for a bitset (long int). You will see that std::bitset<64> is still 8 bits, 65 will take 16 :

size
#include <iostream>
#include <bitset>

int main(int argc, char** argv)
{
    std::bitset<64> myset { 0b1100'1100 };
    std::bitset<65> bigset { 0b1100'1100 };

    std::cout << sizeof(myset) << std::endl;
    std::cout << myset << std::endl;
    std::cout << sizeof(bigset) << std::endl;
    std::cout << bigset << std::endl;
}   

Output is :

output
1
2
3
4
8
0000000000000000000000000000000000000000000000000000000011001100
16
00000000000000000000000000000000000000000000000000000000011001100

65 bits just do not fit in 8 bytes, so it needs two longs (16 bytes) and at the next wrap (129) it will become 24 bytes. So this does present a better solution for bit fields since you would have to name each field in a bitfield struct differently :

bitfields
struct BIT {
    unsigned char f1 : 1;
    unsigned char f2 : 1;
    unsigned char f3 : 1;
    unsigned char f4 : 1;
    unsigned char f5 : 1;
    unsigned char f6 : 1;
    unsigned char f7 : 1;
    unsigned char f8 : 1;
};

Important

One of the few cases you need unsigned int is while doing bit manipulation like this.

At the minimal advantage that this struct will be 1 bytes in size.

Important

A bitset is only larger in granularity (8 bytes).

Besides that bitset offers some convenience functions like :

  • test()
  • set()
  • reset()
  • flip()

bitwise operators

Note that left and right do what you would expect in base 10, so the rightmost bit is the least significant. Shifting also follow those directions:

bitwise operators
1
2
3
4
5
6
7
8
unsigned int x = 0x12345678;
unsigned int y;
std::cout << std::hex << x << std::endl;

y = 16*x; // multiple by 0x10 shifts 1 hex digit
std::cout << std::hex << y << std::endl;
x <<= 4;    // same as left shift 4 bits 
std::cout << std::hex << x << std::endl;

This will print 23456780 two times. Note again that std::bin does not work so i did a hex example instead.

Important

Also note that ^ (xor) does exist for bitwise operations.

Note that the bitwise operators also work on bitset<>. This can be handy to manipulate multiple bits at a time. bitset operators only handle 1 bit at a time.

bitset operators
constexpr unsigned char mask0{ 0b0000'0001 }; // represents bit 0
constexpr unsigned char mask1{ 0b0000'0010 }; // represents bit 1

std::bitset<8> flags{ 0b0000'0101 }; // 8 bits in size means room for 8 flags

// the functions need multiple calls 
flags.flip(1);  // 00000111
flags.flip(2);  // 00000011

// the bit operators can do it shorter 
flags ^= (mask1 | mask2); // back to 00000101

// or ...
flags ^= 0b110;  // yields 0000011 again \

// and also this works as expected 
flags |= 0b111110;
std::cout << flags << std::endl;    // 00111111 

All fine :

Important

So it seems you can mix plain integers with bitset<>s.

two's complement

Important

two's complement negative numbers are the inverse of the positive numbers with 1 added

This +1 eases arithmetic and prevents 0 having two representations (all 1's and all 0's).

This means these are the border values for chars :

val base inverted +1 (-val) so
0 00000000 11111111 00000000 0 == 0
1 00000001 11111110 11111111 1 != -1
127 01111111 10000000 10000001 127!=-127
128 10000000 01111111 10000000 -128==-128

The sign bits are the leftmost bit, so 128 becomes -128 not 128

Warning

There is a proposal to make two's complement mandatory in C++, but until that it is still compiler dependent.

Important

However gcc, msvc and llvm use two's complement !!

chapter S4 scope and more types

global scope

You can use the global namespace operator to use a global name that was masked.

global scope
1
2
3
4
5
6
7
8
int x = 100;

int main()
{
    int x= 999;

    std::cout << x << std::endl;     # 999
    std::cout << ::x << std::endl;   # 100

coercion

Implicit type conversion in assignments and expressions can lead to weird results :

prints 4294967291
std::cout << 5u - 10;

This does not print -5, but 4294967291. This has to do with the rules of coercion

Evaluating arithmetic expressions

Important

in general variables are promoted (floating types) or widened(integer types) to larger types when needed.

Important

If the operation will convert a larger type to a smaller one it is called conversion.

arithmetic expressions
1
2
3
4
5
6
7
// promotions
long l { 64 }; // widen integer to long
double d{ 0.12f }; // promote the float 0.12 into a double

// conversions
double d{ 3 }; // convert integer 3 to a double (between different types)
short s{ 2 }; // convert integer 2 to a short (from larger to smaller type)

When evaluating expressions, the compiler breaks each expression down into individual sub expressions. The arithmetic operators require their operands to be of the same type. To ensure this, the compiler uses the following rules:

If an operand is an integer that is narrower than an int, it undergoes integral promotion (as described above) to int or unsigned int. If the operands still do not match, then the compiler finds the highest priority operand and implicitly converts the other operand to match. The priority of operands is as follows:

  • long double (highest)
  • double
  • float
  • unsigned long long
  • long long
  • unsigned long
  • long
  • unsigned int
  • int (lowest)

Chars and shorts are promoted already in the first steps, which leaves this priority list exactly from the one with the 'most room' for a positive number.

So in the case in the introduction :

  • 5u - 10
  • == with types :
  • unsigned int - signed int
  • == the highest priority wins, so 10 get's promoted
  • unsigned int - unsigned int
  • 5u - 10u = -5u
  • == -5 unsigned wraps around to 4294967291

So ...

Warning

be careful with mixing signed and unsigned integers.

If in doubt you can use typeinfo to see what the type is. It might be worth noting that g++ uses these codes for the types :

  • signed int : i
  • unsigned int : j
typeinfo
1
2
3
4
5
6
7
8
#include <typeinfo>

std::cout << typeid(5u).name() << std::endl;
# prints j
std::cout << typeid(10).name() << std::endl;
# prints i
std::cout << typeid((5u - 10)).name() << std::endl;
# prints j

So both operands 5u and 10 fit into an unsigned, but the answer does not.

enum

enums will evaluate to a plain integer, but it does not work the other way around.

enum
1
2
3
4
5
6
enum my_enum {
    EERSTE,TWEEDE,DRIEDE,VIERDE
};

my_enum my; 
my = 2;           // invalid conversion from ‘int’ to ‘my_enum’

Important

Each enumerated type is a distinct type .

So you also cannot assign a value from one enum to another. You can use smaller types for enums to save space :

enum
1
2
3
4
5
6
7
8
enum Color : std::uint_least8_t
{
    COLOR_BLACK,
    COLOR_RED,
    // ...
};

std::cout << sizeof(Color) << "n";     // prints 1

enum classes

Enums are not typesafe, for instance you cannot assign a value to another enum but comparison just evaluates to integers :

enum
enum Color
{
    RED, // RED is placed in the same scope as Color
    BLUE
};

enum Fruit
{
    BANANA, // BANANA is placed in the same scope as Fruit
    APPLE
};

Color color = RED; // 
Fruit fruit = BANANA; // 
Fruit notallowed = RED   // This will fail !

// but :
if (color == fruit) // The compiler will compare a and b as integers
    std::cout << "color and fruit are equaln"; // and find they are equal!
else
    std::cout << "color and fruit are not equaln";

Though the compiler does complain in the above example, it is just a warning. If you use 'enum class' it will not compile at all, because now you have to use the scope operator to reach the values :

enum class
enum class Color // "enum class" defines this as a scoped enumeration instead of a standard enumeration
{
    RED, // RED is inside the scope of Color
    BLUE
};

enum class Fruit
{
    BANANA, // BANANA is inside the scope of Fruit
    APPLE
};

Color color = Color::RED; // note: RED is not directly accessible any more, we have to use Color::RED
Fruit fruit = Fruit::BANANA; // note: BANANA is not directly accessible any more, we have to use Fruit::BANANA

if (color == fruit) // compile error here, as the compiler doesn't know how to compare different types Color and Fruit
     std::cout << "color and fruit are equal";
else
     std::cout << "color and fruit are not equal";

Important

'enum struct' can also be used instead of 'enum class'

Warning

enum classes have no implicit conversion to int, so you cannot use them as array indices !!

scope
enum class StudentNames
{
    KENNY, // 0
    KYLE, // 1
    STAN, // 2
    BUTTERS, // 3
    CARTMAN, // 4
    WENDY, // 5
    MAX_STUDENTS // 6
};

int main()
{
    // both next lines will fail compiling
    int testScores[StudentNames::MAX_STUDENTS]; // allocate 6 integers
    testScores[StudentNames::STAN] = 76;

    // with cast they will work :
    int testScores[static_cast<int>(StudentNames::MAX_STUDENTS)]; // allocate 6 integers
    testScores[static_cast<int>(StudentNames::STAN)] = 76;

    return 0;
}

I think in this case it does not pay off using enum classes.

typedef and aliases

There is a new construct for defining type aliases in C++11 and later.

typedef
1
2
3
typedef int my_int_t
// new form :
using my_int_t = int

In this format it does not seem as much of an improvement, but :

alias
1
2
3
typedef int(*fnc)(int);
// same as :
using fnc = int(*)(int);

Here the name is extracted to the left leaving it slightly more readable.

structs

Note that in C++ you don't have to use struct for every declaration :

structs
struct X {
    int a;
    float b;
};

int main()
{
    // in C 
    struct X x={};
    // in C++  
    X x{1,0.0};   // initialized (all 0)
    X y;    // undefined values 
    return 0;
}

Note that whenever you use ={} or {} all variables you don't specify are made 0. If you don't use {} at all everything is uninitialized.

You can also since C++11 specify the values in the struct :

with values
1
2
3
4
5
6
7
struct X {
    int   a;       // without = 
    float b={2.2};  // with =
};

X x{10};    // { 10, 2.2} 
X y;        // { 1, 2.2 }

Now all members you don't specifically fill are given those values, also when you don't initialize !!

Warning

This only counts for C++14 and up.

auto

auto is used to deduce the type of a variable from it's assignment expression. From that point on however the type is fixed.

auto
auto a = 1.1;       // a is a double !! (no f)
a = "string";       // error

chapter C.5 control flow

Not much...

chapter 6 arrays

fixed arrays

Declaring an array with dimensions should only be done with a compile time constant.

fixed array
enum elms { ARRAY_LEN=5 };
bool a=true;

// ok :
int array1[10];
int array2[ARRAY_LEN];
int array3[a];  // yep.. sizeof(array3) = 4 !

// not ok, but g++ seems to allow it
int x;
std::cin >> x;
int array4[x];      // std::cout << sizeof(array4) will give 4*x

Warning

That last one does work on g++ but is NOT standard c++ !

Arrays are not initialized by default because of performance, but using {} will set them to 0. Explicit initialization :

nullify
1
2
3
4
int prime[5] = { 2, 3, 5, 7, 11 };
// or uniform
int p[10] {}; // 0,0,0,0,0,0,0,0,0,0
int q[10] {1,2}; // 1,2,0,0,0,0,0,0,0,0

passing arrays

Arrays are never completely copied but passed by reference. So if you don't want a function to modify the values pass it as const.

by reference
// even though prime is the actual array, within this function it should be treated as a constant
void passArray(const int prime[5])
{
    // so each of these lines will cause a compile error!
    prime[0] = 11;
    prime[1] = 7;
    prime[2] = 5;
    prime[3] = 3;
    prime[4] = 2;
}

Important

Arrays are always converted to pointers when passed to function.

by reference
f(int x[], int *y, int z[10]) { 
    std::cout << sizeof(x) << std::endl; // 8
    std::cout << sizeof(y) << std::endl; // 8
    std::cout << sizeof(z) << std::endl; // 8
} 

int main() {
    int a[] = {1,2,3,4};

    std::cout << sizeof(a) << std::endl; // 16
    f(a,a,a);
    return 0;
} 

Plus y and z will get a warning like :

Warning

‘sizeof’ on array function parameter ‘z’ will return size of ‘int*’

Important

Note that the size of an int here is 4 bytes but the size of a pointer to int is 8 bytes !!

array length

The ole way of dividing sizeof(arr)/sizeof(elm) still works, but c++17 introduced std::size() and better, signed in std::ssize() which returns the number of elements.

std::ssize() also works on std::vector and std::array so why not use that consistently.

Important

use std::ssize() to get the number of elements in array like datatypes.

Warning

std::[s]size() is only available in c++17 and up. For std::array and std::vector you could use .size() before c++17. For [] you will have to do the sizeof trick.

sorting

The std::sort() function does work on arrays and on earlier compilers.

sorting
#include <algorithm> // for std::sort
#include <iostream>
#include <iterator> // for std::size
#include <array>

int main()
{
    int array[]{ 30, 50, 20, 10, 40 };

    // begin()/end()/sort() all working on int[]
    std::sort(std::begin(array), std::end(array));

    for (int i{ 0 }; i < 5; ++i)
        std::cout << array[i] << ' ';

    std::cout << 'n';

    return 0;
}

multidimensional arrays

They order in which you expect (row-major order) : int array[row][cols];

multidimensional
// initializing is also as expected

int array[2][3] { 
    { 11,12,23},
    { 21,22,23} 
};

// same as : 
int array[2][3] { 
    11,12,23,
    21,22,23 
};

// even this works 
int array[][2] {
     11,12,13,
     21,22,23
} ;

std::cout << array[1][1] << std::endl; // would print 21

Internally the array is contiguous, so this just works. However if you would make this an array of int [6] you would have to do the row/col math yourself.

Important

you can only leave the first dimension open to be calculated by the compiler.

This seems counter intuitive but if you draw out the array format it all makes sense.

layout
1
2
3
4
5
6
int cols =3;
int [][cols] = {
    { 1,2,3 },
    { 4,5,6 }, 
    ... // adds easier than a column
};

std::string_view

If you create a string from another string.

3 strings
char *s = "Hallo";
std::string str;

There are three copies of "Hallo", the string literal, the value of s and the value of str. They cannot point to the same data because they might get changed.

Important

Might is used on purpose because const std::string would ALSO get a copy !

To save space in cases like this you could use string_view() which is view only as you may have guessed.

Important

string_view is c++17 and up

string_view
char s[] {"Hello"};
std::string_view str;
const std::string_view cstr{str};

std::cout << (void *)s << std::endl;
std::cout << (void *)str.data() << std::endl;
std::cout << (void *)cstr.data() << std::endl;

// but beware of this :
s[1] = 'a';
std::cout << cstr << std::endl;

If we used std::string, all three pointers would have a different value but now they are the same, which also means the value can be changed ("Hello->Hallo");

output
1
2
3
4
5
0x7ffde185472a
0x7ffde185472a
0x7ffde185472a

Hallo

Other operations on the string_view window :

  • remove_prefix(1); // "allo"
  • remove_suffix(2); // "al"

Warning

But the window only closes !

pointers

Important

When declaring put the * next to the variable name, except for function return types. There it is clearer near the return type.

declaration
int *a;     // clearer that a is a pointer
int* a();   // clearer that a returns a pointer

Important

The & operator does not return an address but returns a pointer holding an address, with it's type derived from the argument. (int * for example).

Important

once assigned (int *ptr = &val) : ptr is the same as &val, and *ptr is TREATED the same as val.

nullptr

C define NULL as a macro for 0, but there is a better way using nullptr.

nullptr is also 0 but it has the correct type for the pointer you use it for. For instance this snippet

nullptr
1
2
3
4
5
double* A = nullptr;
char* B = nullptr;

cout << typeid(A).name() << 'n';
cout << typeid(B).name() << 'n';

Will print

output
Pd
Pc

And if you feed those to c++filt :

c++filt
1
2
3
4
c++filt -t Pd
double*
c++filt -t Pc
char*

Neat, whenever you encounter an old NULL, just replace it with nullptr.

Then there is also a std::nullptr_t version and that is type that is only capable of holding a nullptr. The only use is probably when you want a function parameter to exactly be a nullptr type !?

usage

  • Arrays are implemented using pointers. Pointers can be used to iterate through an array (as an alternative to array indices) (covered in lesson 6.8).
  • They are the only way you can dynamically allocate memory in C++ (covered in lesson 6.9). This is by far the most common use case for pointers.
  • They can be used to pass a large amount of data to a function in a way that doesn't involve copying the data, which is inefficient (covered in lesson 7.4)
  • They can be used to pass a function as a parameter to another function (covered in lesson 7.8).
  • They can be used to achieve polymorphism when dealing with inheritance (covered in lesson 12.1).
  • They can be used to have one struct/class point at another struct/class, to form a chain. This is useful in some more advanced data structures, such as linked lists and trees.

pointers vs arrays

An example:

arrays and pointers
1
2
3
4
5
int array[5] = { 9, 7, 5, 3, 1 };
// print address of the array's first element
std::cout << "Element 0 has address: " << &array[0] << 'n';
// print the value of the pointer the array decays to
std::cout << "The array decays to a pointer holding address: " << array << 'n';

Important

It’s a common fallacy in C++ to believe an array and a pointer to the array are identical. They’re not. In the above case, array is of type “int[5]”, and it’s “value” is the array elements themselves. A pointer to the array would be of type “int *”, and its value would be the address of the first element of the array.

In these definitions :

definitions
int arr[10] = {1,2,3,4,5};
int *parr = arr;
declaration name [1] sizeof() arr/parr &arr/&parr
int arr[10] static array 2 40 ptr arr[0] ptr arr[0]
int *parr pointer to int 2 8 ptr arr[0] ptr ptr to arr[0]

So the type information int[10] gets lost (decay) when assigning parr.

Warning

in function calls int x[] or will be converted to int *x

Important

Use f(int *arr) instead of f(int arr[]) because it will be turned into the first form anyway.

Important

arrays inside structs and classes do NOT decay !

So if you really want functions to have the array information intact you can pass a struct containing the array :

maintain array information
struct arrx {
    int x[10];
};

void f(struct arrx xx)
{
    printf("%d %dn", sizeof(xx.x), xx.x[4]);
    // 40 5
}

int main()
{
    struct arrx ax { {1,2,3,4,5} };
    f(ax);
}

Otherwise you would have to pass the length as another parameter and do all checking manually.

example of array/ptr behavior

Here we alter a local parameter b in main just by calling function f() !. Note that this does not generate an error :

array vs ptr
#include <iostream>

int func(int a[14]) // a just 'decays' into int * !!
{
    printf("%pn", a);
    a[5] = 22;
    a[6] = 23;
    a[7] = 24;
    a[8] = 25;

    return 0;
}

//A simple regex test
int main()
{
    int a[5] = { 1,2,3,4,5};
    int b = 111;
    func(a);
    printf("a is %d, %dn", a[5], b);
}

This will print:

output
a = 22, 24

What happens is that this is the stackframe of main at the time of calling func

explanation
a = [1,2,3,4,5,?,?,?];  // aligned on 4 or 8 bytes
b = 111

The stack frame for func will be

explanation
a = [1,2,3,4,5,?,?,?,?,?,?,?,?,?,?,?]; // 15 long

At return it will be altered to :

explanation
a = [1,2,3,4,5,22,23,24,25,?,?,?,?,?,?,?]; // 15 long

And b is just overwritten as 24, a number that happens to be aligned there.

pointer arithmetic

When adding integers to a pointer, the size of the pointer type including alignment is added. This process is called 'scaling'.

Important

The compiler actually translates arr[x] into *(arr + x)

So array indices always correspond to addition of the index to element 0.

strings

You can dynamically alter strings if created as a static array.

alter static string
#include <stdio.h>

int main()
{
    // creates an array[5] and fills it wih "Alex0";
    char myName[] = "Alex";
    myName[2] = 'y';

    // creates a pointer to wherever "Alex" is in memory
    // probably in readonly storage !!
    char *ptrName = "Alex";
    // ptrName[2] = 'y'; would crash !

    printf("%sn", myName);
    return 0;
}

This prints Alyx, and it is like you create myname with calloc(5) and then filled it with "Alex0".

dynamic memory

chapter 14 exceptions

throw and catch work based on the type thrown. And you can throw any type of exception you want.

exception
try {
    std::cout << "try block n";
    throw (-1);
    throw (1); // would also print - !!
    throw (1u); // this would print + 
} catch (unsigned int x) {
    std::cout << "+ n";
} catch (int x) {
    std::cout << "- n";
};

Here you would see try block and a - sign because -1 and 1 resolve to signed int, note that of course throw(1) and throw(1u) are never called.

Important

You can catch fundamental types by value, but for efficiency it is better to catch all other types by reference (move semantics).

Important

The compiler will not perform implicit conversion as in assignments.

So these throws are not caught, and the programs ends with the given error.

exception
1
2
3
4
5
6
7
8
9
try {
    std::cout << "try block n";
    throw(1.1); // terminate called after throwing an instance of 'double'
    throw('a'); // terminate called after throwing an instance of 'char'
} catch (unsigned int x) {
    std::cout << "+ n";
} catch (int x) {
    std::cout << "- n";
};

If an exception is thrown, nothing else is executed until the exception is handled. If the try block does not handle it or if it is outside of a try block the function is stopped and popped, and the same check is done one stack frame higher.

This process continues up to main, and is called stack-unwinding. If main also does not handle the exception main terminates with most OS's mentioning the unhandled exception but this is implementation dependent.

To prevent this you could at least provide a 'catch-all' handler in main.

catch-all
#include <iostream>
int main()
{
    try
    {
        throw 5; // throw an int exception
    }
    catch (double x)
    {
        std::cout << "We caught an exception of type double: " << x << 'n';
    }
    catch (...) // catch-all handler
    {
        std::cout << "We caught an exception of an undetermined typen";
    }
}

exception specifiers

Warning

Only in C++11/14, not in later versions C++17 and up.

Functions may have this syntax in older C++ versions, specifying if they may, or will not throw exceptions :

older c++ versions
1
2
3
int doSomething() throw(); // does not throw exceptions
int doSomething() throw(double); // may throw a double
int doSomething() throw(...); // may throw anything

As said, this is removed in C++17 but might be encountered in older code.

noexcept

This is the only specifier that survived in C++17 and up. It specifies that a function does NOT throw an exception. Destructors are always implicitly noexcept since they cannot throw exceptions.

Note that a noexcept function can still use exceptions internally and deeper, it will just not itself or let a subfunction throw any. So a catch-all block is probably of use in noexcept functions.

Important

noexcept is useful for making clear that a function does not throw, and for some compiler optimizations

constructors

If an exception occurs in a constructor, you can either handle it or throw an exception to the calling function. If you handle it the construction can just continue If you throw an exception all constructed members are destructed again, but the destructor is never called, because the object was never fully constructed.

exception classes

Exception types are not subject to implicit conversions, so a throw char will not be caught by catch(int).

exception class
class Base {
public:
    Base(){};
};

class Derived : public Base {
public:
    Derived(){};
};

int main() noexcept
{
    try {
        std::cout << "try block n";
        throw Base();    // will print base. Base is not a Derived
        throw Derived(); // would print derived, ...
        // or  base if catch Derived was disabled, Derived is a Base !
    } catch (Derived &derived) {    
        std::cout << "derived n";
    } catch (Base &base) {
        std::cout << "base n";
    };
    return 0;
}

If you would swap the catch clauses for Derived/Based, both would print base, because order matters !. However the g++ compiler will complain if you do so.

using the std::exceptions

Important

First of all, do not throw std::exception itself, because it is a base type for the others.

Some useful std exceptions are :

  • invalid_argument(const std::string what)
  • format_error() : not meant for json errors, but useful anyway
  • runtime_error() : also a base class but fits better than some subclasses
  • overflow_error()
  • range_error():

see visit for more

you can always derive from std::exception (or std::runtime_error o.i.d) and make one yourself that fits perfect.

rethrowing exceptions

For instance if you just log an error but not handle it you can throw a new exception or even the same one. You are in a catch block, not a try block so it won't be caught again !!

However just rethrowing the exception object might slice it if it was caught as a base of an deeper class :

re-throw
1
2
3
4
5
6
7
8
9
try
{
    throw Derived();
}
catch (Base& b)
{
   throw b; // the Derived object gets sliced here as a Base() !
   throw; // this would throw b as Derived() 
}

This can be avoided by not throwing an object (throw b;) but just using plain throw.

Function try blocks

These are needed if you want to handle an exception where you cannot reach the function, like in constructors :

constructor
1
2
3
4
5
6
7
8
class B : public A
{
public:
    B(int x) : A(x)
    {
        // What happens if creation of A fails and we want to handle it here?
    }
};

:A (x) calls the constructor for A but we have no room to put try linenums="1" catch() around it, so the exception will occur in function with new B() .

You can do this with an alternate syntax called function try block.

function try block
class B : public A
{
public:
    B(int x) try : A(x) // note addition of try keyword here
    {
    }
    catch (...) // note this is at same level of indentation as the function itself
    {
         std::cerr << "Exception caughtn";

         // an exception MUST be thrown here, otherwise the compiler
         throw;
         // will rethrow the same exception implicitly !!
    }
};

Note that the catch is actually outside of the constructor block {}

Warning

function try blocks must have an exception thrown up the stack again. If you don't the compiler will !

destructors

Important

Exceptions should NEVER be thrown in destructor because that entangles destruction and stack unwinding.

G++ will warn if you try :

Warning

warning: throw will always call terminate() [-Wterminate]

chapter C 5 control flow

Control flow statements with 1 line are actually converted to 1 line blocks. Note :

converted to block
// this would complain about a being redefined 
if (x==1)
    int a = 1;
else
    int a = 11;

// but it does not because it is converted to : 
if (x==1) {
    int a = 1;
} else { 
    int a = 11;
} 
// and now you cannot use a outside both these if's
// same goes for the other loops :
for (;;) 
    int x = 11;

// can't use x here

chained if statements

If you chain if else together it is like you nest them further each time

else if
1
2
3
4
5
6
7
8
if (a< 10) 
    printf("10n");
else if (a < 100) 
    printf("100n");
else if (a < 1000) 
    printf("1000n");
else 
    printf("moren");

Is equivalent to :

same as
1
2
3
4
5
6
7
8
9
if (a< 10) {
    printf("10n");
} else { if (a < 100)
    printf("100n");
} else { if (a < 1000)
    printf("1000n");
} else { 
    printf("moren");
} 

Which should be indented as :

indented
if (a< 10) {
    printf("10n");
} else {
    if (a < 100) {
        printf("100n");
    } else {
        if (a < 1000) {
            printf("1000n");
        } else { 
            printf("moren");
        }
    }
}

dangling else

Important

the 'dangling' else always belongs to the last unmatched if statement in the same block

init statement

New in stdc++17 is the init statement before the condition.

initialize in if
1
2
3
4
// works with g++ -std=c++17 
if (int x=0; x==0) {
    printf("x is %dn", x);
}

The variable will only be usable in the if statement and it's bodies (if and(or) else)

switch

Note that a switch is a compound statement, but a case is NOT.

Warning

case statements are NOT implicit block statements !!!

Important

You can declare but NOT initialize variables inside a switch. You will get these jump crosses initialization errors.

Jumping cross an initialization leaves that variable in an undefined state, it really skips all the statements in between and does not stop to handle the initialization ! So it is forbidden.

So .. :

jumps
switch (1)
{
    int a; // okay, declaration is allowed before the case labels
    int b = 5; // illegal, here you would jump from the switch to case 1
                // skipping setting b to 5
    case 1:
        int y; // okay, declaration is allowed within a case
        y = 4; // okay, this is an assignment
        break;

    case 2:
        y = 5; // okay, y was declared above, so we can use it here too
                // this proves case code is not implicitly put between {}
        break;

    case 3:
        int z = 4; // illegal, switch (4) would skip this initialization!
        break;

    case 4:
        int a;      // illegal, a already defined
        printf("z is now %dn", z); // what is z ? NOT 4 !

    case 5: 
    {
        // of course nothing prevents you from making a block yourself
        int a=10;  // now this is allowed, because a goes out of scope
        // and nothing get's crossed.
    }

    default:
        std::cout << "default case" << std::endl;

    printf("%dn", a); // no problem, printed if no breaks encountered
}

printf("%dn", a); // sadly the switch IS a {block} so x is not defined

chapter 15 smart pointers

example

smart pointers
1
2
3
printf("%p %pn", sp, sp.get()); 
# output :
# 0x7fffffffd440 (nil)

enable_shared_from_this

If you use smart pointers you can assign these almost anywhere, as long as you match up the type. However, you have no vote in the type of 'this' inside the class. For instance

this type
1
2
3
4
5
6
7
8
9
class Trip { 
    std::shared_ptr<Vehicle> vehp;
};

class Vehicle { 
    void linkTrip(Trip *trip) {
        trip->vehp = this;  // ayy vehp is shared_ptr, this = Vehicle !!
    } 
};

This can be solved by two steps : subclass Vehicle from std::enable_shared_from_this and calling the member function shared_from_this().

enable_shared_from_this
1
2
3
4
5
class Vehicle : public std::enable_shared_from_this<Vehicle> {
    void linkTrip(Trip *trip) {
        trip->vehp = shared_from_this(); // this hooks up the correct ptr
    } 
};