Table des matières

Modern C++

Le C++ moderne existe depuis C++11, c'est-à-dire depuis 2011.

Certains IDE d'intérêt pour C++:

Ressources

Setup Linux

sudo apt install build-essential

Compile by Command-Line Interface

Having a main.cpp file in current directory:

$ g++ -Wall -std=c++14 main.cpp -o main

Structure d'un programme C++

Preprocessor Directives

#include <iostream>
#include "myfile.h"
 
#if
#elif
#else
#endif
 
#ifdef
#ifndef
#define
 
#line
#error
#pragma

Comments

// Single line comment
 
/*
  Multi-line comment
*/

main() function

Deux versions:

int main() {
  // code
  
  return 0;
}
int main(int argc, char *argv[]) {
  // code
  
  return 0;
}

argc est le argument count et argv sont les paramètres passés par le système d'exploitation.

Namespaces

Utiliser un namespace:

std::cout << "Some text";
using namespace std;

// ... code

cout << "Some text";

Qualified using namespace variant:

using std::cout;  // Utiliser seulement ce qui est nécessaire

// ... code

cout << "Some text";

Variables

VariableType variableName;

Règles:

int age; // uninitialized
 
int age = 21; // C-like initialization
int age = (21); // Constructor initialization
int age {21}; // C++11 list initialization syntax

Global variables

#include <iostream>
 
int age {18}; // Global
 
int main() {
  int age {16}; // local, shadowing global one 
}

Primitive Data Types

Liste non-exhautive (voir Introduction to fundamental data types) :

Initialization:

char middle_name {'J'};  // single quote for char

long people_on_earth = 7'600'000'000; // sera un overflow
long long people_on_earth {7'600'000'000}; // ' pour séparer les miliers, depuis C++14

float car_payment {401.23};

long double large_amount {2.7e120};

bool game_over {false};
cout << "Value of game_over is " << game_over << endl; // Value of game_over is 0

sizeof

sizeof(int);

int age {31};
sizeof(age);

sizeof(long long);

Constantes de minimums et maximums:

Constants

Literal constants:

12 - integer
12U - unsigned int
12L - long
12LL - long long

12.1 - double
12.1F - float
12.1L - long double

Caracter literal constants : \n, \r, etc

Declared constants:

const int age {31};  // Declared constant
#define pi 3.1415926   // defined constant, ne pas utiliser

Arrays

Multi-dimentional arrays

int movie_rating [3][4]
{
  {0,3,4,5},
  {0,2,5,5},
  {0,3,1,5}
};

Tableau statique

Points importants:

int arr1[5];  // tableau non initialisé
int arr2[5]{}; // éléments initialisés avec 0
int arr3[5]{ 1, 2, 3, 4, 5 }; // initialisé avec les valeurs spécifiés
int *p = arr3;
*(p + 2) = 800; // Le 3e élément (index 2) aura la valeur 800

Passer un tableau à une fonction

void Print(int *ptr, int size) {
    for (int i = 0; i < size; ++i) {
        std::cout << ptr[i] << ' ';
        //std::cout << *(ptr + i) << ' '; // moins lisible, donc la façon précédente est préférable
    }
}
 
Print(arr3, sizeof(arr3) / sizeof(int));

En tant que référence:

template<typename T, int size>
void Print(T(&ref)[size]) {
    for (int i = 0; i < size; ++i) {
        std::cout << ref[i] << ' ';
    }
}
 
int(&ref)[5] = arr3;
Print(arr);

Vectors

#include <vector>

vector <char> vowels;
vector <int> test_scores;

Characters and Strings

C-Style Characters

C-Style Strings

  1. Sequence of characters
    1. Contiguous in memory
    2. Implemented as an array of characters
    3. Terminated by a null character
      1. null → character with a value of zero
    4. Referred to as zero or null terminated strings

String Literals

  1. Sequence of characters in double quotes
  2. Constant
  3. Terminated with null character

Fonctions

#include <cctype>

Test Functions
isalpha(c) True if c is a letter
isalnum(c) True if c is a letter or a digit
isdigit(c) True if c is a digit
islower(c) True if c is a lowercase letter
isprint(c) True if c is a printable character
ispunct(c) True if c is a punctuation character
isupper(c) True if c is a an uppercase letter
isspace(c) True if c is a whitespace
Conversions
tolower(c)
toupper(c)
char my_name[] = {"My name"};
char my_name[8];
 
my_name = "Frank";   // Error
strcpy(my_name, "Frank");  // OK

Exemples de fonctions C-Style

#include <cstring>
 
char str[80];
 
strcpy(str, "Hello ");
strcat(str, "there");  // Concatenate
cout << strlen(str);  //11
strcmp(str, "Another"); // > 0

cstdlib

#include <cstdlib>

Inclus des fonctions pour convertir des chaines C-Style à integer, float, long, etc.

C++ Strings

std::string is a class in the STL

#include <string>
using namespace std;
 
string s1;  // Empty
string s2 {"My name"};
string s3 {s2};
string s4 {"My name", 3};  // "My "
string s5 {s3, 0, 2};      // "My"
string s6 (3, '-');        // "---"
 
// Concatenation
 
string part1 {"C++"};
string part2 {"is a powerful"};
string sequence = part1 + " " + part2 + " language";
 
sequence = "C++" + " is powerful";  // Illegal: two C-Style strings can't concatenate this way

Regex

Définir un regex:

std::regex regex(R"(\d{2}));

Functions

Using builtin functions:

#include <iostream>
#include <cmath>
 
using namespace std;
 
int main() {
    double num {};
 
    cout << "Enter number (double): ";
    cin >> num;
 
    cout << "The sqrt of " << num << " is: " << sqrt(num) << endl;
 
    return 0;
}
#include <iostream>
#include <cmath>
#include <ctime>
 
using namespace std;
 
int main() {
    int random_number {};
    size_t count {10};
    int min {1}; // lower bound (inclusive)
    int max {6}; // upper bound (inclusive)
 
    cout << "RAND_MAX on my system is: " << RAND_MAX << endl;
    srand(time(nullptr));
 
    for (size_t i {1}; i <= count; ++i) {
        random_number = rand() % max + min;
        cout << random_number << endl;
    }
 
    return 0;
}

Function definition

 
int function_name () {
  statements(s);
  return 0;
}
 
void function_name (int a, std::string b) {
  statements(s);
  return; // optional
}

Les fonctions doivent être définies avant d'être appelées.

Function Prototypes

Define functions before calling them:

Use function prototypes:

int function_name(); // prototype, no parameters
int function_name(int); // prototype
int function_name(int a); // we can specify parameter name or not
 
int function_name(int a) {
  statements(a);
  return 0;
}

Function parameters

Dans la définition d'une fonction, on parle de paramètres, mais quand on appelle la fonction on parle d'arguments.

Quand on passe des données à une fonction, elles sont passées par valeur par défaut.

Formal vs actual parameters:

Default Argument Values

Dans le prototype:

double calc_cost(double base_cost, double tax_rate = 0.06);
void print(int = 100);

On peut le faire aussi dans l'implémentation de la fonction, mais pas aux deux endroits. Il est préférable de le faire sur le prototype.

Les valeurs par défaut doivent être placés à la fin de la liste des paramètres.

double calc_cost(double base_cost = 100.0, double tax_rate); // ne fonctionne pas

Overloading Functions

int add_numbers(int, int);
double add_numbers(double, double);
 
int main() {
    cout << add_numbers(10, 20) << endl;
    cout << add_numbers(10.0, 20.0) << endl;
    return 0;
}

Passing Arrays to Functions

Les tableaux sont passés par référence, donc le changement de valeur dans la fonction a un effet sur les valeurs du tableau externe.

#include <iostream>
#include <cmath>
 
using namespace std;
 
void zero_array(int numbers [], size_t size);
void print_array(int numbers [], size_t size);
 
int main() {
    int my_numbers[] = { 1,2,3,4,5 };
    zero_array(my_numbers, 5);
    print_array(my_numbers, 5);
 
    return 0;
}
 
void zero_array(int numbers [], size_t size) {
    for (size_t i {0}; i < size; ++i) {
        numbers[i] = 0;
    }
}
 
void print_array(int numbers [], size_t size) {
    for (size_t i {0}; i < size; ++i) {
        cout << numbers[i] << endl;
    }
}

Pour nous aider, selon les cas, on peut définir la fonction avec un paramètre const:

void print_array(const int numbers [], size_t size) {
  // ...
  numbers[0] = 0; // Mènera à une erreur du compilateur
}

Pass by reference

void pass_by_ref1(int &num);
 
 
void pass_by_ref1(int &num) {
    num = 1000;
}
 
 
int main() {
    int num {10};
 
    cout << "num before calling pass_by_ref1: " << num << endl; // 10
    pass_by_ref1(num);
    cout << "num after calling pass_by_ref1: " << num << endl; // 1000
 
    return 0;
}

Scope rules

Local or Block scope:

Static local variables:

Global scope:

using namespace std;
 
void local_example();
void global_example();
void static_local_example();
 
int num {300};    // Global variable - declared outside any class or function
 
int main() {
    int num {100};  // Local to main
 
    {   // creates a new level of scope
        int num {200};  // local to this inner block
    }
    return 0;
}

Inline functions

inline int add_numbers(int a, int b) { // definition
  return a + b;
}
 
int main() {
  int result {0};
  result = add_numbers(100, 200); // call
  return 0;
}

Recursive Functions

A recursive function is a function that calls itself, either directly or indirectly through another function.

#include <iostream>
 
using namespace std;
 
unsigned long long factorial(unsigned long long);
 
unsigned long long factorial(unsigned long long n) {
    if (n == 0) {
        return 1;              // base case
    }
 
    return n * factorial(n-1); // recursive case
}
 
int main() {
    cout << factorial(3) << endl;       // 6
    return 0;
}
#include <iostream>
 
using namespace std;
 
unsigned long long fibonacci(unsigned long long n);
unsigned long long fibonacci(unsigned long long n) {
    if (n <= 1) {
        return n; // base cases
    }
 
    return fibonacci(n-1) + fibonacci(n-2); // recursion
}
 
int main() {
    cout << fibonacci(5) << endl;   // 5
    return 0;
}

Function Pointers

float Add(float x, float y)
{
  return x + y;
}
 
float Substract(float x, float y)
{
  return x - y;
}
 
int main()
{
  float(*FnPtr)(float, float);  // Declaration of function pointer FnPtr
  FnPtr = Add;  // or '&Add', both works
  float result = FnPtr(3.1f, 8.3f);
  std::cout << result << std::endl;
 
  // We can reassign FnPtr, provided that function referenced has same signature
  FnPtr = &Substract;  // Using amphersand here, more verbose, but again, same as without it
}

Another example:

const char* GetErrorMessage(int errorNo)
{
  switch (errorNo)
  {
    case 0:
      return "Error 0";
    case 1:
      return "Error 1";
    case 2:
      return "Error 2";
    default:
      return "Unknown error"  
  }
}
 
int main() 
{
  const char* (*Pfn)(int) = GetErrorMessage;
  std::cout << Pfn(1) << std::endl;
}

Function pointers as Arguments

using PFN = float(*)(float, float);
 
float Operation(float x, float y, PFN pfn)
{
    if (pfn == nullptr)
    {
        std::cout << "Invalid operation" << std::endl; 
        return 0;
    }
    float result = pfn(x, y);
 
    return result;
}
 
int main()
{
    Operation(1, 2, Add);
}

Instead of declaring a function pointer type, we can do inline in function, but declaring a type might be preferable:

float Operation(float x, float, y, float(*pfn)(float, float))
{
    // implementation
}

Two ways of declaring a function pointer type:

typedef float(*PFN)(float, float);
 
float Operation(float x, float y, PFN) { ... }

or

using PFN = typedef float(*)(float, float);
 
float Operation(float x, float y, PFN pfn) { ... }

Pointers and References

Qu'est-ce qu'un pointeur:

Qu'est-ce qui peut être à cette adresse ?

Pour utiliser la valeur dont le pointeur pointe, on doit connaitre son type.

Pourquoi utiliser des pointeurs ?

Déclarer des pointeurs

variable_type *pointer_name; // syntaxe générale
 
int *int_ptr;
double* double_ptr;
char *char_ptr;
string *string_ptr;

Les deux possibilités de l'astérisque sur le type ou sur le nom sont acceptés. Généralement on le met sur le nom de variable/pointer.

Initialiser des pointeurs qui pointent nul part.

int *int_ptr {};
int *int_ptr {nullptr};

Endianess

Illustration du Little Endian:

Ici on voit que l'adresse du pointeur ptr est 0x001FFCC0 et sa valeur est l'adresse de data, c'est-à-dire 0x001FFCCC. Mais dans la mémoire, si on inspecte la valeur à cet endroit, on verra CC FC 1F 00.

Accéder/Stocker à l'adresse du pointeur

Il faut utiliser l'opérateur adresse (address operator): &

int num{10};
cout << "Value of num is: " << num << endl; // 10
cout << "sizeof of num is: " << sizeof num << endl; // 4
cout << "Address of num is: " << &num << endl; // 0x7fffd0421194 (garbage)

sizeof d'un pointeur

int *p;
cout << "\nValue of p is: " << p << endl;   // 0x7f8ccaa45048 (garbage)
cout << "Address of p is: " << &p << endl;  // 0x7ffd39db3f70
cout << "sizeof of p is: " << sizeof p<< endl; // 8
 
p = nullptr;
cout << "\nValue of p is: " << p << endl; // 0

Stocker une adresse dans un pointeur

Le compilateur va s'assurer que l'adresse stockée dans un pointer est du bon type.

int score{10};
double high_temp{100.7};
 
int *score_ptr {nullptr};
 
score_ptr = &score;
cout << "Value of score is: " << score << endl;
cout << "Address of score is: " << &score << endl;
cout << "Value of score_ptr is: " << score_ptr << endl;
score_ptr = &high_temp;     // Compiler error

String

string s1 {"Some string"};
string *p1 {&s1};

Déréférencement d'un pointeur

Accéder à la valeur où le pointeur pointe → deferencing a pointer

Par exemple, si un pointeur nommé score_ptr a une adresse valide, on peut donc accéder à sa valeur à l'adresse contenue dans score_ptr en utilisant l'opérateur de déférencement: *.

int score {100};
int *score_ptr {&score};
 
cout << *score_ptr << endl; // 100
 
*score_ptr = 200;
cout << *score_ptr << endl;  // 200
cout << score << endl;       // 200

Dynamic Memory Allocation

Allocating storage from heap at runtime.

int *int_ptr {nullptr};
int_ptr = new int;        // allocate the int on the heap
cout << int_ptr << endl;  // use it
delete int_ptr;           // release it

Utilisation de new []:

int *array_ptr {nullptr};
size_t size{0};
 
cout << "How big do you want the array? ";
cin >> size;
 
array_ptr = new int[size];
 
// ...
 
delete [] array_ptr;

Lien entre un tableau et un pointeur

int scores[] {100, 95, 89};
int *score_ptr {scores};

Pointer subscript notation

cout << score_ptr[0] << endl; // 100
cout << score_ptr[1] << endl; // 95
cout << score_ptr[2] << endl; // 89

Pointer offset notation

cout << *score_ptr << endl;       // donne 100, mais exemple addr est 0x73f610
cout << *(score_ptr + 1) << endl; // donne 95, addr est 0x73f614 (ajoute 4 car 4 est le size d'un int
cout << *(score_ptr + 2) << endl; // donne 89, addr est 0x73f618

Arithmétique des pointeurs

int scores[] {100, 95, 89, 68, -1}; // -1 est une valeur sentinelle
int *score_ptr {scores};
 
while (*score_ptr != -1) {
  cout << *score_ptr << endl;
  score_ptr++;
}
 
// Ou:
 
while (*score_ptr != -1) {
  cout << *score_ptr++ << endl;
}

Constant pointers

On ne peut pas changer la valeur (l'adresse) du pointeur constant.

int high_score {100};
int low_score {65};
int *const score_ptr {&high_score};
 
*score_ptr = 86;  // OK
score_ptr = &low_score; // Error

Passing pointers to a function

void double_data(int *int_ptr) {
   *int_ptr *= 2;	
}
 
int main() {
    int value {10};
    int *int_ptr {nullptr};
 
    cout << "Value: " << value << endl; // 10
    double_data(&value);
    cout << "Value: " << value << endl; // 20
 
    int_ptr = &value;
    double_data(int_ptr);
    cout << "Value: " << value << endl; // 40    
}
voi display(vector<string> *v) {
  (*v).at(0) = "Funny";
}

Returning a pointer from a Function

int *create_array(size_t size, int init_value = 0) {
   int *new_storage {nullptr};
   new_storage = new int[size];   
   for (size_t i{0}; i < size; ++i)
      *(new_storage + i) = init_value;
   return new_storage;
}

Pitfalls

Reference

Qu'est-ce qu'une référence:

int num {100};
int &ref {num};
 
vector<string> stooges {"Larry", "Moe", "Curly"};
 
for (auto str: stooges)     
    str = "Funny";              // str is a COPY of the each vector element
 
for (auto str:stooges)        // No change
    cout << str << endl;
 
for (auto &str: stooges)  // str is a reference to each vector element
   str = "Funny";
 
for (auto const &str:stooges)   // notice we are using const
   cout << str << endl;            // now the vector elements have changed

L-values and R-values

L-value:

int x {100}; // x is an l-value
string name; // name is an l-value
 
100 = x; // 100 is not a l-value

R-Values can be assigned to l-values explicitly

int x {100}; // 100 is a r-value
int y = x + 200 // (x+200) is a r-value

Quand utiliser un pointeur vs une référence

OOP - Classes et objets

Definition

class Account {
  std::string name;
  double balance;
 
  bool withdraw(double amount);
  bool deposit(double amount);
};

Création d'objets

Account account1;
Account account2;
 
Account accounts[] { account1, account2 };
 
std::vector<Account> accounts { account1 };
accounts.push_back(account2);

Access Class Members

If we have an object (dot operator)

Account account1;
 
account1.balance;
account1.deposit(100.0);

If we have a pointer to an object (member of a pointer operator)

Account *account1 = new Account();
 
(*account1)->balance;
(*account1)->deposit(100.0);
Account *account1 = new Account();
 
account1->balance;
account1->deposit(100.0);

Access modifiers

class Player {
private:
  std::string name;
  int health;
  int xp;
public:
  void talk(std::string text_to_say);
  bool is_dead();
};

Implementing Member methods

Account.h

class Account {
private:
    // attributes
    std::string name;
    double balance;
 
public:
    // methods
    // declared inline
    void set_balance(double bal) { balance = bal; }
    double get_balance() { return balance; }
 
    // methods will be declared outside the class declaration
    void set_name(std::string n);
    std::string get_name();
 
    bool deposit(double amount);
    bool withdraw(double amount);
};

Account.cpp

#include "Account.h"
 
void Account::set_name(std::string n) {
    name = n;
}
 
std::string Account::get_name() {
    return name;
}
 
bool Account::deposit(double amount) {
    // if verify amount
    balance += amount;
    return true;
}
 
bool Account::withdraw(double amount) {
    if (balance-amount >= 0) {
        balance -= amount;
        return true;
    } else {
        return false;
    }
}

main.cpp

// Section 13
// Implementing member methods 2
#include <iostream>
#include "Account.h"
 
using namespace std;
 
int main() {
    Account frank_account;
    frank_account.set_name("Frank's account");
    frank_account.set_balance(1000.0);
 
    if (frank_account.deposit(200.0)) {
        cout << "Deposit OK" << endl;
    }
    else {
        cout << "Deposit Not allowed" << endl;
    }
 
    return 0;
}

Include guard

#ifndef _ACCOUNT_H_
#define _ACCOUNT_H_
 
// Account class declaration
 
#endif

Ou

#pragma once

Constructeurs et Destructeurs

Constructors:

Destructors:

class Player {
private:
   std::string name;
   int health;
   int xp;
public:
    void set_name(std::string name_val) { 
        name = name_val; 
    }
 
    // Overloaded Constructors
 
    Player() { 
        cout << "No args constructor called"<< endl;
    }
 
    Player(std::string name) { 
        cout << "String arg constructor called"<< endl;
    }
 
    Player(std::string name, int health, int xp) {
        cout << "Three args constructor called"<< endl; 
    }
 
    ~Player() {
        cout << "Destructor called for " << name << endl; 
    }
};

Creating objects

{
  Player slayer;
  Player frank {"Frank", 100, 4};
  Player hero {"Hero"};
  // use the objects
} // 4 destructors called
 
Player *enemy = new Player("Enemy", 100, 0);
delete enemy; // destructor called

If we don't provide constructor and/or destructor, C++ will automatically provide default constructor/destructor that are empty.

Default constructor

Player frank;
Player *enemy = new Player;

If implemented

// Section 13
// Default Constructors
#include <iostream>
#include <string>
 
using namespace std;
 
class Player {
private:
   std::string name;
   int health;
   int xp;
 
public:
    void set_name(std::string name_val) { 
        name = name_val; 
    }
 
    std::string get_name() {
        return name;
    }
 
    Player() {
        name = "None";
        health = 100;
        xp = 3;
    }    
};
 
int main() {
    Player hero; // this is calling the default constructor, initializing name, health and xp
    return 0;
}

Overloading Contructor

Constructor initialization lists

class Player {
private:
   std::string name {"XXXXXXX"};
   int health;
   int xp;
public:
// Overloaded Constructors
    Player();
    Player(std::string name_val);
    Player(std::string name_val, int health_val, int xp_val);
};
 
Player::Player() 
    : name{"None"}, health{0}, xp{0} {
}
 
Player::Player(std::string name_val) 
   : name{name_val}, health{0}, xp{0} {
}
 
Player::Player(std::string name_val, int health_val, int xp_val) 
    : name{name_val}, health{health_val}, xp{xp_val} {
    // empty body, but code can added obviouly
}
 
int main() {   
    Player empty;
    Player frank {"Frank"};
    Player villain {"Villain", 100, 55};
 
    return 0;
}

Delegating Constructors

#include <iostream>
#include <string>
 
using namespace std;
 
class Player {
private:
   std::string name;
   int health;
   int xp;
public:
// Overloaded Constructors
    Player();
    Player(std::string name_val);
    Player(std::string name_val, int health_val, int xp_val);
};
 
Player::Player() 
    : Player {"None", 0, 0} {
        cout << "No-args constructor" << endl;
}
 
Player::Player(std::string name_val) 
   : Player {name_val, 0, 0}  {
           cout << "One-arg constructor" << endl;
}
 
Player::Player(std::string name_val, int health_val, int xp_val) 
    : name{name_val}, health{health_val}, xp{xp_val} {
            cout << "Three-args constructor" << endl;
}
 
int main() {
    Player empty;
    Player frank {"Frank"};
    Player villain {"Villain", 100, 55};
 
    return 0;
}

Default Constructor Parameters

#include <iostream>
#include <string>
 
using namespace std;
 
class Player
{
private:
   std::string name;
   int health;
   int xp;
public:
    Player(std::string name_val = "None", int health_val = 0, int xp_val = 0);
  //  Player() {}    // Will cause a compiler error
};
 
Player::Player(std::string name_val, int health_val, int xp_val) 
    : name{name_val}, health{health_val}, xp{xp_val} {
            cout << "Three-args constructor" << endl;
}
 
int main() {   
    Player empty;
    Player frank {"Frank"};
    Player hero {"Hero", 100};
    Player villain {"Villain", 100, 55};
 
    return 0;
}

Copy Constructor

Pass object by-value

Player hero {"Hero", 100, 20};
 
void display_player(Player p) {
  // p is a COPY of hero in this example
  // ... use p
  // Desctructor for p will be called
}
 
display_player(hero);

Return object by value

Player enemy;
 
Player create_super_enemy() {
  Player an_enemy {"Super Enemy", 1000, 1000};
  return an_enemy; // A copy of 'an_enemy' is returned.
}
 
enemy = create_super_enemy();

Construct one object based on another

Player hero {"Hero", 100, 100};
 
Player another_hero {hero}; // A copy of 'hero' is made

Default Copy Constructor

Best pratices

Declaring the Copy Constructor

Type::Type(const Type &source);
Player::Player(const Player &player);

Implementation:

Player::Player(const Player &source)
  : name{source.name},
    health {source.health},
    xp {source.xp} {
}

Shallow Copy

Default Copy Constructor

Deep Copy

Deep copy: create new storage and copy values.

Deep::Deep(const Deep &source) {
  data = new int; // allocate storage
  *data = *source.data;
}

Deep copy constructor - delegating constructor

Deep::Deep(const Deep &source)
  : Deep {*source.data} {
    cout << "Copy constructor - deep" << endl;
}

Move Constructor

When is it useful?

R-Values:

#include <iostream>
#include <vector>
 
using namespace std;
 
class Move {
private:
    int *data;
public:
    void set_data_value(int d) { *data = d; }
    int get_data_value() { return *data; }
 
    Move(int d); // Constructor
    Move(const Move &source); // Copy Constructor
    Move(Move &&source) noexcept; // Move Constructor
    ~Move(); // Destructor
};
 
Move::Move(int d)  {
    data = new int;
    *data = d;
    cout << "Constructor for: " << d << endl;
}
 
// Copy ctor
Move::Move(const Move &source)
    : Move {*source.data} {
        cout << "Copy constructor  - deep copy for: " << *data << endl;
}
 
//Move ctor
Move::Move(Move &&source) noexcept 
    : data {source.data} {
        source.data = nullptr;
        cout << "Move constructor - moving resource: " << *data << endl;
}
 
Move::~Move() {
    if (data != nullptr) {
        cout << "Destructor freeing data for: " << *data << endl;
    } else {
        cout << "Destructor freeing data for nullptr" << endl;
    }
    delete data;
}
 
int main() {
    vector<Move> vec;
    vec.push_back(Move{10});
    vec.push_back(Move{20});
    // ..
    vec.push_back(Move{80});
    return 0;
}

The this pointer

Const with classes

const Player villain {"Villain", 100, 55};

comme paramètre:

void display_player_name(const Player &p) {
  cout << p.get_name() << endl;
}
 
display_player_name(villain);  // ERROR : compiler asumes that get can modify object/name

Const methods:

class Player {
  private:
    // ...
  public:
    std::string get_name() const;
    // ...
};

Static class members

class Player {
  private:
    static int num_players;
  public:
    static int get_num_players();
};
 
int Player::num_players = 0;

Static method has only access to static members:

int Player::get_num_players() {
  return num_players;
}

Struct vs Classes

class Person {
  std::string name;
  std::string get_name(); // Why if name is public?
};
 
Person p;
p.name = "Joe";  // Compiler error - private
cout << p.get_name(); // Compiler error - private
struct Person {
  std::string name;
  std::string get_name(); // Why if name is public?
}
 
Person p;
p.name = "Joe";  // OK, public
cout << p.get_name(); // OK, public

Another example of struct:

struct Person {
  std::string name;
  int age;
}
 
Person p1 {"Curly", 31};
Person p2 {"Moe", 39};

Friends of a class

Considerations:

class Player {
    friend void display_player(Player &p);
    std::string name;
    int health;
    int xp;
  public:
    // ...
};
void display_player(Player &p) {
  std::cout << p.name << std::endl;  // can change private members
  std::cout << p.health << std::endl;
  std::cout << p.xp << std::endl;
}

Member function of another class:

class Player {
    friend void Other_class::display_player(Player &p);
    std::string name;
    int health;
    int xp;
  public:
    // ...
};
class Other_class {
  ...
  public:
    void display_player(Player &p) {
      std::cout << p.name << std::endl;
      // ...
    }
};

Another class as a friend

class Player {
    friend class Other_class;
};

Operator Overloading

What is Operator Overloading?

See Introduction to operator overloading for more details.

Copy assignment operator

Syntax:

Type &Type::operator=(const Type &rhs);

Example:

Mystring &Mystring::operator=(const Mystring &rhs);
 
s2 = s1; // we write this
s2.operator=(s1); // operator= method is called

Implementation:

Mystring &Mystring::operator=(const Mystring &rhs) {
  if (this == &rhs) {
    return *this;
  }
 
  delete [] str;
  str = new char[std::strlen(rds.str + 1];
  std::strcopy(str, rhs.str);
 
  return *this;
}

Move Assignment Operator (=)

Type &Type::operator=(Type &&rhs);

Instead of copying data to a new memory location, we steal the pointer of the source (RHS).

Declaration in .h file:

Mystring(Mystring &&rhs); // move constructor, old version, not present, only for demonstration
Mystring &operator=(Mystring &&rhs); // move constructor, overloaded

Implementation:

Mystring &Mystring::operator=(Mystring &&rhs) {
  std::cout << Using move assignment" << std::endl;
 
  if (this == && &rhs) {
    return *this;
  }
 
  delete [] str;
 
  str = rhs.str;
  rhs.str = nullptr;
 
  return *this;
}

Unary operator as member methods (++, --, -, !)

Syntax:

ReturnType Type::operatorOp();

Examples:

Number Number::operator-() const;
Number Number::operator++() const; // pre-increment
Number Number::operator++(int) const; // post-increment
bool Number::operator!() const;

Inheritance

What is it and why is it used?

class Account {
  // balance, deposit, withdraw
};
 
class Savings : public Account {
  // interest rate, specialized withdraw
};

Terminology

C++ Derivation Syntax

class Base {
  // Base class members
};
 
class Derived: access-specifier Base {
  // Derived class members
};

Constructors and Destructors

Passing Arguments to base constructors

class Base {
public:
  Base();
  Base(int);
  // ...
};
 
Derived::Derived(int x)
  : Base(x) { // optional initializers for Derived
  // code
};

Complete implementation:

#include <iostream>
 
using namespace std;
 
class Base {
private:
    int value;
public:
   Base() : value {0}  { 
            cout << "Base no-args constructor" << endl; 
    }
    Base(int x)  : value {x} { 
            cout << "Base (int) overloaded constructor" << endl;
    }
   ~Base() { 
       cout << "Base destructor" << endl;
    }
};
 
class Derived : public Base {
private:
    int doubled_value;
public:
    Derived()
        : Base {}, doubled_value {0} {
            cout << "Derived no-args constructor " << endl; 
    }
    Derived(int x) 
        : Base{x},  doubled_value {x * 2} { 
            cout << "Derived (int) constructor" << endl; 
    }
    ~Derived() { 
        cout << "Derived destructor " << endl; 
    } 
};
 
int main() {
   //  Derived d;
   Derived d {1000};
    return 0;
}

Copy constructor

Derived(const Derived &other)
    : Base(other), doubled_value {other.doubled_value} {
        cout << "Derived copy constructor" << endl;     
}

Redefining Base Class Methods

class Account {
public:
    void deposit(double amount) { balance += amount; }
};
 
class Savings_Account: public Account {
public:
  void Savings_Account::deposit(double amount) { // Redefine Base class method
    amount = amount + (amount * int_rate/100); 
    Account::deposit(amount);                   // invoke call Base class method
  }
};

Static Binding of method calls

Base b;
b.deposit(10.0);  // Base::deposit
 
Derived d;
d.deposit(10.0);  // Derived::deposit
 
Base *.ptr = new Derived();
ptr->deposit(10.0);  // Base::deposit

Multiple Inheritance

class Department_Chair
  : public Faculty, public Administrator {
 
  // code  
};

Polymorphism

An non-polymorphic example - Static Binding

Account a;
a.withdraw(1000);    // Account::withdraw()
 
Savings b;
b.withdraw(1000);    // Savings::withdraw()
 
Checking c;
c.withdraw(1000);    // Checking::withdraw()
 
Trust d;
d.withdraw(1000);    // Trust::withdraw()
 
Account *p = new Trust();
p->withdraw(1000);   // Account::withdraw
                     // should be
                     // Trust::withdraw()

void display_account(const Account &acc) {
  acc.display();  // will always use Account::display
}
 
Account a;
display_account(a);
 
Savings b;
display_account(b);
 
Checking c;
display_account(c);
 
Trust d;
display_account(d);

An polymorphic example - Dynamic Binding

withdraw method is virtual in Account

Account a;
a.withdraw(1000);    // Account::withdraw()
 
Savings b;
b.withdraw(1000);    // Savings::withdraw()
 
Checking c;
c.withdraw(1000);    // Checking::withdraw()
 
Trust d;
d.withdraw(1000);    // Trust::withdraw()
 
Account *p = new Trust();
p->withdraw(1000);   // Trust::withdraw

display method is virtual in Account

Below, the display_account() will always call the display method based on the object's type at runtime.

void display_account(const Account &acc) {
  acc.display();
}
 
Account a;
display_account(a);
 
Savings b;
display_account(b);
 
Checking c;
display_account(c);
 
Trust d;
display_account(d);

Code example

Example using static binding:

#include <iostream>
 
class Base {
public:
  void say_hello() const {
    std::cout << "Hello - I'm a Base class object" << std::endl;
  }
};
 
class Derived: public Base {
public:
  void say_hello() const {
    std::cout << "Hello - I'm a Derived class object" << std::endl;
  }
};
 
void greetings(const Base &obj) {
  std::cout << "Greetings: ";
  obj.say_hello();
}
 
int main() {
    Base b;
    b.say_hello(); // "Hello - I'm a Base class object"
 
    Derived d;
    d.say_hello(); // "Hello - I'm a Base class object"
 
    greetings(b); // "Greetings: Hello - I'm a Base class object"
    greetings(d); // "Greetings: Hello - I'm a Base class object"
 
    Base *ptr = new Derived();
    ptr->say_hello(); // "Hello - I'm a Base class object"
 
    std::unique_ptr<Base> ptr1 = std::make_unique<Derived>();
    ptr1->say_hello(); // "Hello - I'm a Base class object"
 
    delete ptr;
 
    return 0;
}

Using a Base class pointer

Account *p1 = new Account();
Account *p2 = new Savings();
Account *p3 = new Checking();
Account *p4 = new Trust();
 
vector<Account *> accounts {p1, p2, p3, p4};
 
for (auto acc_ptr: accounts) {
  acc_ptr->withdraw(1000);
}
 
// delete pointers
#include <iostream>
#include <vector>
 
class Account {
public:
    virtual void withdraw(double amount) {
        std::cout << "In Account::withdraw" << std::endl;
    }
    virtual ~Account() {  }
};
 
class Checking: public Account  {
public:
    virtual void withdraw(double amount) {
        std::cout << "In Checking::withdraw" << std::endl;
    }
 
    virtual ~Checking() {  }
};
 
class Savings: public Account  {
public:
    virtual void withdraw(double amount) {
        std::cout << "In Savings::withdraw" << std::endl;
    }
    virtual ~Savings() {  }
};
 
class Trust: public Account  {
public:
    virtual void withdraw(double amount) {
        std::cout << "In Trust::withdraw" << std::endl;
    }
    virtual ~Trust() {  }
};
 
int main() {
    std::cout << "\n === Pointers ==== " << std::endl;
    Account *p1 = new Account();
    Account *p2 = new Savings();
    Account *p3 = new Checking();
    Account *p4 = new Trust();
 
    p1->withdraw(1000);
    p2->withdraw(1000);
    p3->withdraw(1000);
    p4->withdraw(1000);
 
    std::cout << "\n === Array ==== " << std::endl;
    Account *array [] = {p1, p2, p3, p4};
    for (auto i=0; i<4; ++i) {
        array[i]->withdraw(1000);
    }
 
    std::cout << "\n === Array ==== " << std::endl;
    array[0] = p4;
    for (auto i=0; i<4; ++i) {
        array[i]->withdraw(1000);
    }
 
    std::cout << "\n === Vector ==== " << std::endl;
    std::vector<Account *> accounts {p1, p2, p3, p4};
    for (auto acc_ptr: accounts) {
        acc_ptr->withdraw(1000);
    }
 
    std::cout << "\n === Clean up ==== " << std::endl;
    delete p1;
    delete p2;
    delete p3;
    delete p4;
 
    return 0;
}

Virtual Functions

Declaring virtual functions:

class Account {
public:
  virtual void withdraw(double amount);
  // ...
};

Declaring virtual functions (derived classes):

class Checking: public Account {
public:
  virtual void withdraw(double amount);
  // ...
};

Virtual Destructors

Solution / Rule:

class Account {
public:
  virtual void withdraw(double amount);
  virtual ~Account();
  // ...
};
 
class Savings: public Account  {
public:
    virtual void withdraw(double amount);
    virtual ~Savings() {  }
};

Using the Override Specifier

Illustrated problem:

#include <iostream>
 
class Base {
public:
    virtual void say_hello() const {
        std::cout << "Hello - I'm a Base class object" << std::endl;
    }
    virtual ~Base() {}
};
 
class Derived: public Base {
public:
    virtual void say_hello() {             // Notice I forgot the const
        std::cout << "Hello - I'm a Derived class object" << std::endl;
    }
    virtual ~Derived() {}
};
 
 
int main() {
    Base *p1 = new Base();
    p1->say_hello();
 
    Derived *p2 = new Derived();
    p2->say_hello();
 
    Base *p3 = new Derived();
    p3->say_hello();
 
    return 0;
}

In the output, third line should say Hello - I'm a Derived class object:

Hello - I'm a Base class object
Hello - I'm a Derived class object
Hello - I'm a Base class object

Correction:

class Derived: public Base {
public:
    virtual void say_hello() const override {
        std::cout << "Hello - I'm a Derived class object" << std::endl;
    }
    virtual ~Derived() {}
};

The final specifier

C++11 provides the final specifier

Syntax:

class MyClass final {
  // ...
};

Syntax on derived class:

class Derived final: public Base {
  // ..
}

Method level

class A {
public:
  virtual void do_something();
};
 
 
class B: public A {
public:
  virtual void do_something() final;
};
 
 
class C: public B {
public:
  virtual void do_something();  // Compiler error: Can't override
};

Using Base class references

Account a;
Account &ref = a;
ref.withdraw(100);  // Account::withdraw
 
Trust t;
Account &ref1 = t;
ref1.withdraw(100);  // Trust::withdraw
void do_withdraw(Account &account, double amount) {
  account.withdraw(amount);
}
 
Account a;
do_withdraw(a, 100);  // Account::withdraw
 
Trust t;
do_withdraw(t, 100);  // Trust::withdraw

Pure Virtual Functions and Abstract Classes

Abstract class:

Concrete class:

Abstract Base Class:

Pure virtual function:

Abstract Classes as Interfaces

Printable example:

class Printable {
  friend ostream &operator << (ostream &, const Printable &obj);
public:
  virtual void print(ostream &os) const = 0;
  virtual ~Printable() {};
  // ...
};
 
ostream &operator<<(ostream &os, const Printable &obj) {
  obj.print();
  return os;
}

To be Printable:

class AnyClass: public Printable {
  virtual void print(ostream &os) override {
    os << "Hi from AnyClass";
  }
};

Usage:

AnyClass *ptr = new AnyClass();
cout << *ptr << endl;
 
void function1 (AnyClass &obj) {
  cout << obj << endl;
}
 
void function2 (Printable &obj) {
  cout << obj << endl;
}
 
function1(*ptr);  // "Hi from AnyClass"
function2(*ptr);  // "Hi from AnyClass"

We can use capital I plus underscore to designate an Interface:

class I_Shape {
  // ...
};

Smart Pointers

Issues with raw pointers:

What are smart pointers

Requires include:

#includes <memory>
{
  std::unique_ptr<SomeClass> ptr = ...;
  ptr->method();
  cout << (*ptr) << endl;
}
 
// Out of scope, ptr will be destroyed automatically when no longer needed

RAII - Resource Aquisition Is Initialization

Unique Pointer

unique_ptr

{
  std::unique_ptr<int> p1 { new int {100} };
  std::cout << *p1 << std::endl; // 100
  *p1 = 200;
  std::cout << *p1 << std::endl; // 200
}

Methods:

Using move:

std::unique_ptr<int> p1;
std::unique_ptr<int> p2 = { new int {100}};
p1 = p2; // Compiler Error
p1 = std::move(p2);  // p2 is now ''nullptr''

make_unique

Since C++14

{
  std::unique_ptr<int> p1 = make_unique<int>(100);
 
  std::unique_ptr<Account> p2 = make_unique<Account>("Joe", 500);
 
  auto p3 = make_unique<Player>("Hero", 100, 100);
}

Another example:

std::vector<std::unique_ptr<Account>> accounts;
 
accounts.push_back( make_unique<Checking_Account>("James", 1000));
accounts.push_back( make_unique<Savings_Account>("Billy", 4000, 5.2));
accounts.push_back( make_unique<Trust_Account>("Bobby", 5000, 4.5));
 
for (const auto &acc: accounts) {
    std::cout << *acc << std::endl;
}

Shared Pointer

shared_ptr

{
  std::shared_ptr<int> p1 { new int {100} };
  std::cout << *p1 << std::endl; // 100
  *p1 = 200;
  std::cout << *p1 << std::endl; // 200
}

use_count

std::shared_ptr<int> p1 { new int {100} };
std::cout << "Use count: "<< p1.use_count() << std::endl; 		// 1
 
std::shared_ptr<int> p2 { p1 };                    // shared ownwership
std::cout << "Use count: "<< p1.use_count() << std::endl; 		// 2
 
p1.reset();	// decrement the use_count; p1 is nulled out
std::cout << "Use count: "<< p1.use_count() << std::endl; 		// 0 
std::cout << "Use count: "<< p2.use_count() << std::endl; 		// 1

make_shared

std::shared_ptr<int> p1 = std::make_shared<int>(100); // use_count: 1
std::shared_ptr<int> p2 { p1 };                       // use_count: 2
std::shared_ptr<int> p3;
p3 = p1;                                              // use_count: 3

Weak Pointer

weak_ptr

Problem

Circular or cyclic reference

#include <iostream>
#include <memory>
 
using namespace std;
 
class B;    // forward declaration
 
class A {
    std::shared_ptr<B> b_ptr;
public:
    void set_B(std::shared_ptr<B> &b) {
        b_ptr = b;
    }
    A() { cout << "A Constructor" << endl; }
    ~A() { cout << "A Destructor" << endl; }
};
 
class B {
    std::shared_ptr<A> a_ptr;
public:
    void set_A(std::shared_ptr<A> &a) {
        a_ptr = a;
    }
    B() { cout << "B Constructor" << endl; }
    ~B() { cout << "B Destructor" << endl; }
};
 
int main() {
    shared_ptr<A> a  = make_shared<A>();
    shared_ptr<B> b = make_shared<B>();
    a->set_B(b);
    b->set_A(a);
 
    return 0;
}

Output:

A Constructor
B Constructor

Solution

Use one pointer as a weak pointer

#include <iostream>
#include <memory>
 
using namespace std;
 
class B;    // forward declaration
 
class A {
    std::shared_ptr<B> b_ptr;
public:
    void set_B(std::shared_ptr<B> &b) {
        b_ptr = b;
    }
    A() { cout << "A Constructor" << endl; }
    ~A() { cout << "A Destructor" << endl; }
};
 
class B {
    std::weak_ptr<A> a_ptr;     // make weak to break the strong circular reference
public:
    void set_A(std::shared_ptr<A> &a) {
        a_ptr = a;
    }
    B() { cout << "B Constructor" << endl; }
    ~B() { cout << "B Destructor" << endl; }
};
 
int main() {
    shared_ptr<A> a  = make_shared<A>();
    shared_ptr<B> b = make_shared<B>();
    a->set_B(b);
    b->set_A(a);
 
    return 0;
}

Output:

A Constructor
B Constructor
A Destructor
B Destructor

Custom Deleters

void my_deleter(Test *ptr) {
  cout << "In my custom deleter" << endl;
  delete ptr;
}
 
shared_ptr<Test> ptr { new Test{}, my_deleter };

With lambda expression:

shared_ptr<Test> ptr (new Test{100}, [] (Test *ptr) {
  cout << "In my custom deleter" << endl;
  delete ptr;
});

Exception Handling

Basic concepts

Exception Handling:

What causes exceptions?

Exception safe: when code handles exceptions

Terminology

Exception: an object or primitive type that signales that an error occured

Throwing an exception (raising an exception):

Catching an exception (handle exception):

C++ syntax

throw:

try { code that may throw an exception }:

catch(Exception ex) { code to handle exception }:

#include <stdexcept>
...
throw std::runtime_error{"An error occured"};

I/O and Streams

Standard Template Library (STL)

At it's core, it's:

Elements of the STL:

Simple example

#include <vector>
#include <algorithm>
 
std::vector<int> v {1, 5, 3};
 
// sort
 
std::sort(v.begin(), v.end());
for (auto elem: v) {
  std::cout << elem << std::endl;
}
 
// reverse
 
std::reverse(v.begin(), v.end());
for (auto elem: v) {
  std::cout << elem << std::endl;
}
 
// accumulate
int sum {};
sum = std::accumulate(v.begin(), v.end(), 0);

Types of containers

Types of iterators

Types of algorithms

Generic Programming with macros

Macros (#define)

Note: using macros is not necessary good practice.

#define MAX_SIZE 100
#define PI 3.14159
 
if (num > MAX_SIZE) {
  // handle
}
 
double area = PI * r * r; 

Macro with arguments

#define MAX(a, b) ((a > b) ? a : b)
 
std::cout << MAX(10, 20) << std::endl; // 20

Generic Programming with Function Templates

What is a C++ template?

max function as a template function

template <typename T1, typename T2>
void func(T1 a, T2 b) {
  // implementation
}

Example with struct:

template <typename T>
T min(T a, T b) {
    return (a < b) ? a : b;
}
 
template <typename T1, typename T2>
void func(T1 a, T2 b) {
    std::cout << a << " " << b << std::endl;
}
 
struct Person {
  std::string name;
  int age;
  bool operator<(const Person &rhs) const {
    return this->age < rhs.age;
  }
}
 
std::ostream &operator<<(std::ostream &os, const Person &p) {
    os << p.name;
    return os;
}
 
Person p1 {"Curly", 31};
Person p2 {"Moe", 39};
 
Person p3 = min(p1, p2);
std::cout << p3.name << " is younger" << std::endl; // Curly is younger
 
func(p1, p2); // Curly Moe
template <typename T>
void my_swap(T &a, T &b) {
    T temp = a;
    a = b;
    b = temp;
}

Generic Programming with Class Templates

Template classes are typically completely contained in header files. So, we would have the template class in Item.h and no Item.cpp file would be used.

template <typename T>
class Item {
private:
    std::string name;
    T   value;
public:
    Item(std::string name, T value) : name{name}, value{value} 
    {}
    std::string get_name() const {return name; }
    T get_value() const { return value; }
};
 
Item<int> item1 {"Joe", 100};

Creating a Generic Array Template Class

Next code is for demonstration, most of the time use std::array instead.

template <typename T, int N>
class Array {
    int size {N};           // how do we get the N???
    T values[N];        // the N needs to ne known at compile-time!
 
    friend std::ostream &operator<<(std::ostream &os, const Array<T, N> &arr) {
        os << "[ ";
        for (const auto &val: arr.values)
            os << val << " ";
        os << "]" << std::endl;
        return os;
    }
public:
    Array() = default;
    Array(T init_val) {
        for (auto &item: values)
            item = init_val;
    }
    void fill(T val) {
        for (auto &item: values )
            item = val;
    }
    int get_size() const {
        return size;
    }
    // overloaded subscript operator for easy use
    T &operator[](int index) {
        return values[index];
    }
};

STL Containers

Containers:

Containers - Common

Function Description
Default constructor Initializes an empty container
Overloaded constructor Initializes containers with many options
Copy constructor Initializes a container as a copy of another container
Move constructor Moves existing container to new container
Destructor Destroys a container
Copy assignment (operator=) Copy one container to another
Move assignment (operator=) Move one container to another
size Returns the number of elements in a container
empty Returns boolean of if it's empty or not
insert Insert an element into the container

Other functions: swap, erase, clear, begin, end, rbegin, rend, cbegin, cend, crbegin, crend.

Container elements

What types of elements can we store in containers?

STL Iterators

Declaring iterators

Iterator begin and end methods

std::vector<int> vec {1, 2, 3};

std::vector<int>::iterator it = vec.begin();
// or
auto it = vec.begin();

Using iterators

std::vector<int> vec {1, 2, 3};
std::vector<int>::iterator it = vec.begin();
 
while (it != vec.end()) {
  std::cout << *it << " ";
  ++it;
}
// prints: "1 2 3"
 
for (auto it = vec.begin(); it != vec.end(); it++) {
  std::cout << *it << " ";
}

Set:

#include <set>
 
std::set<char> suits {'C', 'H', 'S', 'D'};
auto it = suits.begin();
 
while (it != suits.end()) {
  std::cout << *it << " " << std::end;
  ++it;
}
// prints: "C H S D"

Reverse iterator:

std::vector<int> vec {1, 2, 3};
std::vector<int>::reverse_iterator it = vec.begin();
 
while (it != vec.end()) {
  std::cout << *it << " ";
  ++it;
}
// prints: "3 2 1"

Other iterators

STL Algorithms

Algorithms and iterators

#include <algorithm>

Example with find

std::vector<int> vec {1, 2, 3};
 
auto loc = std::find(vec.begin(), vec.end(), 3);
 
if (loc != vec.end()) {
  std::cout << *loc << std::endl; // 3
}

Example with for_each

struct Square_Functor {
  void operator()(int x) { // overload () operator
    std::cout <<  x * x << " ";
  }
};
 
Square_Functor square;  // Function object
 
std::vector<int> vec {1, 2, 3};
 
std::for_each(vec.begin(), vec.end(), square);

Using a function pointer

void square(int x) {
  std::cout <<  x * x << " ";
}
 
std::vector<int> vec {1, 2, 3};
 
std::for_each(vec.begin(), vec.end(), square);

Using a lambda expression

std::vector<int> vec {1, 2, 3};
 
std::for_each(vec.begin(), vec.end(),
  [](int x) { std::cout <<  x * x << " "; } // lamda
);

Other algorithms

Some other, not all:

Sequence Containers

Associative Containers

Container Adaptors

Associative Containers

Set

Set → See CPP Reference on std::set

#include <set>

std::set<int> s {1, 2, 3, 4, 5};

std::set<std::string> stooges {
  std::string {"Larry"},
  "Moe",
  std::string {"Curly"}
};

s = {4, 6, 8, 8, 10}; // second '8' is ignored

s.insert(7); // returns std::pair, ex: [7, true] (true/false = successfully inserted)
s.insert(8);  // will ignore if already exists in set

Autres méthodes:

Map

Map → See CPP Reference on std::map

#include <map>

std::map<int> s {1, 2, 3, 4, 5};

std::map<std::string, int> m1 {
  {"Larry", 18},
  {"Moe", 25}
};

std::map<std::string, std::string> m2 {
  {"Larry", "King"},
  {"Moe", "Bar"}
};


std::pair<std::string, std::string> p1 = {"James", "Mechanic"};

m2.insert(p1);

m2.insert(std::make_pair("Roger", "Ranger"));


m2["Roger"] = "Cook"; // update
m2["Frank"] = "Singer"; // insert, key doesn't exists

Autres méthodes:

Autres containers

Containers library

Container Adpators

Lambda Expressions

Since C++11

Motivation

Prior to C++11:

Structure of a Lamda Expression

Syntax:

Source

[] () { std::cout << "Hi"; }(); // immediately executed
 
auto l = [] (int x) { std::cout << x; };
 
l(10);

Using pointers

Capture List

An empty capture list would result in a stateless lambda expression.

Examples

Using lambda expression as function parameters

#include <functional>
 
// C++14 -> void below is return type, int is parameter type
void foo(std::function<void(int)> l) {
  l(10);
}
 
// Also C++14
void foo(void (*l)(int)> l) {
  l(10);
}
 
// C++20
void foo(auto l) {
  l(10);
}

Returning lambda expression from functions

#include <functional>
 
std::function<void(int)> foo() {
  return [] (int x) { std::cout << x; };
}
 
// or
 
void (*foo())(int) {
  return [] (int x) { std::cout << x; };
}
 
// or
 
auto foo() {
  return [] (int x) { std::cout << x; };
}

With predicate

void print_if(std::vector<int> nums, bool (*predicate)(int)) {
  for (int i: nums) {
    if (predicate(i)) {
      std::cout << i;
    }
  }
}
 
int main() {
  std::vector<int> nums {1, 2, 3};
 
  print_if(nums, [] (auto x) { return x % 2 == 0; });
  print_if(nums, [] (auto x) { return x % 2 != 0; });
 
  return 0;
}

Stateful Lamba Expressions

Using non-empty capture list.

Transformation of lambda expression by the compiler

Lamda definition

int y {10};
 
auto l = [y] (int x) { std::cout << x + y; };

Compiler-generated closure

class CompilerGeneratedName {
private:
  int y;
public:
  CompilerGeneratedName(int y): y{y} {};
 
  void operator() (int x) const {
    std::cout << x + y;
  }
}

const in generated class avoid modifying member values.

For a large number of capture variables:

Lambdas and the STL

Some code samples to show lambda uses with STL.

for_each

Non-modifying sequence operation, displays each element of nums.

std::vector<int> nums {10, 20, 30, 40, 50};
 
std::for_each(nums.begin(), nums.end(), [] (int num) {
    std::cout << num << " ";
});

transform

std::vector<int> test_scores {93, 88, 75, 68, 65};
int bonus_points {5};
 
std::transform(test_scores.begin(), test_scores.end(), test_scores.begin(), [bonus_points] (int score) {
    return score += bonus_points;
});
 
// Display updated test_scores
for (int score : test_scores) {
  std::cout << score << " ";
}

Enumerations

What is an enumeration?

Structure

enum-key enum-name : enumerator-type { };

Simplest enumerator:

enum {Red, Green, Blue}; // Implicit initialization:
                         // Red = 0, Green = 1 and Blue = 2
enum {Red = 1, Green = 2, Blue = 3}; // Explicit initialization
enum {Red = 1, Green, Blue}; // Explicit/Initialisation initialization
                             // Green = 2 and Blue = 3

Enumetator type is deduced with width of bits required.

Anonymus enum provides no type safety:

enum {Ref, Green, Blue};
 
int my_color;
my_color = Green;  // valid
my_color = 4;      // valid

Named:

enum Color {Ref, Green, Blue};
 
Color my_color;
my_color = Green;  // valid
my_color = 4;      // not valid

Unscoped Enumerations

enum State { Engine_Failure, Inclement_Weather, Nominal };
 
std::underlying_type_t<State> user_input;
std::cin >> user_input;
 
switch (user_input) {
  case Engine_Failure:
    state = State(user_input);
    break;
  case Inclement_Weather:
    state = State(user_input);
    break;
  case Nominal:
    state = State(user_input);
    break;
  default:
    std::cout << "User input is not a valid state.";
 
}

With operator overload:

enum State { Engine_Failure, Inclement_Weather, Nominal };
 
std::istream& operator>>(std::istream& is, State& state) {
  std::underlying_type_t<State> user_input;
 
  is >> user_input;
 
  switch (user_input) {
    case Engine_Failure:
      state = State(user_input);
      break;
    case Inclement_Weather:
      state = State(user_input);
      break;
    case Nominal:
      state = State(user_input);
      break;
    default:
      std::cout << "User input is not a valid state.";
  }
 
  return is;
}
 
// ...
State state;
std::cin >> state;

Scoped Enumerations

Enumerator is qualified.

enum class enum-name : enumerator-type {};

Problem:

enum Whale { Blue, Beluga, Gray };
enum Shark { Greatwhite, Hammerhead, Bull };
 
if (Beluga == Hammerhead) {
  std::cout << "A beluga whale is equivalent to a hammerhead shark.";
}

Name clashes, here with Blue:

enum Whale { Blue, Beluga, Gray };
enum Shark { Greatwhite, Hammerhead, Bull, Blue }; // Error: Blue already defined

Solution:

enum class Whale { Blue, Beluga, Gray };
 
Whale whale = Whale::Beluga;
 
switch (whale) {
  case Whale::Blue:
    std::cout << "Blue whale";
    break;
  case Whale::Beluga:
    std::cout << "Beluga whale";
    break;
  case Whale::Gray:
    std::cout << "Gray whale";
    break;
  default:
    std::cout << "Unknown whale";
}

Using scoped enumerator values

enum class Item { Milk = 350, Bread = 250, Apple = 132 };
 
int milk_code = int(Item::Milk);                    // milk_code = 350
// or
int milk_code = static_cast<int>(Item::Milk);
 
int total = int(Item::Milk) + int(Item::Bread);     // total = 600
std::cout << underlying_type_t<Item>(Item::Milk);   // 350

Assert

#include <cassert>
...
assert(!Empty());

Ressources