Programmation et C++ – Introduction – Programmation



Programmation et C++ – Introduction – Programmation

0 0


BaseProgCxx

Presentation on base of programming and C++

On Github ghlecl / BaseProgCxx

Programmation et C++

Introduction

© 2015 Ghyslain Leclerc, CHUQ-UL

Plan

  • Programmation
  • Philosophie
  • C++ Ligne du temps
  • Lexique de programmation
  • Bases du C++ (Procédurale)
  • Programmation orientée objet en C++
  • Polymorphisme
  • Collisions et namespaces
  • Processus de compilation
  • Conseils en C++

Programmation

Définition générale

La programmation représente la rédaction du code source d'un logiciel.

Écriture

  • Écrire dans un fichier texte (ascii) les instructions pour réaliser une tâche.

Instructions lisibles par un humain

Instructions lisibles par un processeur

  • Compilateur (C/C++, Cobol, Fortran, Pascal...)
  • Interpréteur (Python, Java, C#, Ruby...)

Langage

Facteurs à considérer → choix d'un langage :

  • Facilité d'apprentissage
  • Possibilité multi-plateformes
  • Adoption...
    • générale (support sur sites de questions)
    • de sa communauté (collaboration)
  • Temps nécessaire à écrire un programme
    • Existence de librairies utiles
    • Qualité de la librairie standard
  • Performance

C++

~

Performance

  • De quoi parle-t-on ?
    • Temps entre début et fin du projet
    • Mémoire utilisé
      • memory footprint, executable size
    • Temps
      • Compilation vs exécution

Souvent en conflit

C++

Toujours d'actualité

  • Encore dans le top 10 des langages les plus "cherchés sur le net" / utilisés / appris.

Accessible

  • Compilateur C/C++ disponible sur pratiquement toutes les plateformes:

 

  • Bureau (OSX, Windows, Linux, Sun, etc.)
  • Mobile (iOS, Android)
  • Miniature (Raspberry Py)
  • Embarqué (OS spécialisé de matériel)

Multi-paradigme

Selon Wikipedia :

Un paradigme de programmation est un style fondamental de programmation informatique qui traite de la manière dont les solutions aux problèmes doivent être formulées dans un langage de programmation

Intermédiaire

  • "assembly code" permis (low-level)

 

  • pointeurs→accès direct à la mémoire

 

  • manipulation de bits

 

  • Classes, polymorphismes, etc. (high-level)

Nécessaire en radio-onco

  • Pallier aux faiblesses des logiciels commerciaux.
  • Reliser des logiciels entre eux:
    • corriger les données entre deux logiciels;
    • faire "parler" des logiciels qui ne le font pas d'emblée.
  • Automatisation
    • Standardisation
    • Accélération
  • Nouvelles techniques
  • Recherche

Clinique

Philosophie

Code de qualité

À rechercher en écrivant du code :

  • Simplicité
  • Lisibilité
  • Modularité
  • Design / Elegance
  • Efficacité
  • Clarté

Maintenance

  • Le code peut être utilisé par plus de gens et plus longtemps qu'imaginé au départ.

 

  • Écrire le plus simplement possible, mais pas plus, pour faciliter le maintien.

 

  • Équilibre pas simple à atteindre.

Spécifique à première vue

  • Une bonne partie de la présentation porte sur le C++ et sa syntaxe.

 

  • Néanmoins, les concepts sous-jacents sont retrouvés dans bien des langages et sont plus généraux qu'il pourrait paraître.

C++

Ligne du temps

C++

  • 1979-1980 → Écriture du langage [Version 0]
    • Chez Bell Labs.
    • C with classes (d'où le mythe que C++ = objets)

 

  • ~1985 → Livre de Stroustrup
    • Publication de The C++ Programming Language.
    • Écrit par le créateur du langage.
    • Devient le standard non officiel.

C++

  • Juin 1998 → Document ISO V1   [C++98]
    • Première version "standard" du C++
    • Compilateurs veulent y être le plus fidèles possible.

 

  • Oct. 2003 → Document ISO V2   [C++03]
    • Correction de certains problèmes/oublis.
    • Peu d'ajouts.

C++

  • Oct. 2007 → Technical Report 1 (C++0x TR1)
    • Document qui doit supplanter C++03 tarde à sortir.
    • Création d'une version incomplète pour permettre une évolution du langage.

 

  • Sep. 2011 → Document ISO V3   [C++11]
    • Beaucoup d'ajouts.

C++

  • Déc. 2014 → Document ISO V4   [C++14]
    • Modification mineures du standard 2011
    • Quelques ajouts

 

  • ~2017 → Document ISO V5   [C++17]
    • Beaucoup d'ajouts.

Lexique de programmation

Langage indépendant

  • Variable
  • Expression (expression)
  • Instruction (statement)
  • Programme
  • Sous-routine
  • Scope (portée lexicale)
  • Control flow

Autant que possible...

var_type   var_name;

int        age;
double     sales_tax;
char       my_char;

Variable

  • Symbole qui associe un nom (l'identifiant) à une valeur.
  • La valeur peut être de quelque type de donnée que ce soit (permis par le langage).
int a
average = ( 5 + 7 ) / 2
multi_strings = tokenize( single_string )

Expression

Combinaisons de:

  • valeurs explicites
  • constantes
  • variables
  • opérateurs
  • appels de fonctions

qui sont interprétés selon les règles de précédences d'un langage et qui redonnent une valeur (qu'elle soit utilisée ou pas).

int a   (aucune sous-expression)
average = ( 5 + 7 ) / 2
    ( 5 + 7 -> "sous-expression" 1 )
    ( résultat précédent / 2 -> "sous-expression" 2 )
    ( etc. )

Sous-expression

Une expression peut être composée d'expressions.

Expression               Instruction
----------               -----------
int a                    int a;
( 5 + 7 ) / 2            ( 5 + 7 ) / 2;

Instruction

  • Plus petite unité indépendante de code permise par un langage de programmation.

 

  • Différence parfois petite entre expression et instruction.
  • Nuance n'est pas toujours significative, mais les puristes...  Exemple plus loin.

Exemple de C++

Programme

  • Séquence d'instructions écrites pour faire exécuter une tâche spécifique par un ordinateur.

 

 

  • Implique un ordre pour faire les choses.

 

 

  • Implémente généralement un ou plusieurs algorithme(s).
begin program

    statement 1
    statement 2
    statement 3
    ...
    statement n

end program

Quelques observations

  • On peut vouloir réutiliser une série d'instruction plus d'une fois :
    • au sein d'un même programme;
    • dans deux programmes différents.

 

  • Le code est quelque chose de vivant, qui est modifié avec le temps pour répondre aux besoins changeants.

Conséquence

  • Si le même code est répété 40 fois dans un programme et qu'un changement est nécessaire:
    • il faut changer le code partout
    • risque d'oubli
    • risque de ne pas modifier de façon uniforme

DRY

  • La réutilisation est le Saint-Graal du programmeur.

 

  • Don't   Repeat   Yourself

 

  • Mène (entre autres) aux sous-routines.

Sous-routine

  • Séquence d'instructions destinée à une fonction spécifique, pouvant être appelée par un programme et qui s'exécute de façon autonome.
  • fonction
  • procédure
  • méthode

Fonction

  • Sous-routine qui retourne une (ou plus) valeur.

 

  • Peut (ou pas) modifier ses entrants.
begin function1( no_entrants )
    statement f1_1
    statement f1_2
    return value_f1
end function1

begin function2( 1_entrant )
    value_f2 = 1_entrant + 5
    return value_f2
end function2


begin program
    var1 = call function1()
    call function2( var1 )
end program

Effets secondaires

  • Conséquences "parallèles" d'un appel de fonction (non liée à la valeur de retour).

 

  •  ​Modification :
    • des paramètres entrants
    • d'une (des) variable(s) globale(s)

  • ​Fonction pure si pas d'effets secondaires.

Concept de scope expliqué plus loin

Procédure

  • Sous-routine qui ne retourne pas de valeur.

 

  • Doit nécessairement avoir des effets secondaires, sinon, ne fait littéralement rien.
begin function1                begin procedure1
    statement f1_1                 statement m1_1
    statement f1_2                 // no return statement
    return value_f1            end procedure1
end function1

Méthode

  • ​Sous-routine qui a un contexte attaché (généralement un objet en OO).

 

  • Entrant implicite en plus d'entrant explicite

 

  • Peut (ou pas) retourner une valeur.
    • ​méthode procédure
    • méthode fonction

Scope (portée)

Selon Wikipedia :

En informatique, la portée (scope en anglais) d'un identifiant est l'étendue au sein de laquelle cet identifiant est lié.
In other parts of the program the name may refer to a different entity (it may have a different binding), or to nothing at all (it may be unbound).
int varGlobale;

namespace ns
{
int varLocale_Namespace;
}

void fn( int input )
{
    int varLocale_Fonction;
    if( true )
    {
        int varLocale_If;
    }
    // varLocale_If non disponible ici
    // varLocale_Namespace non disponible sauf si qualifiée (ns::varLocale_Namespace)
}
// varLocale_Fonction non disponible ici...

Exemples

Control flow

  • Commande qui contrôle l'ordre dans lequel les différentes instructions d'un algorithme ou d'un programme informatique sont exécutées.

 

  • Permet l'exécution :
    • non-linéaire
    • conditionnelle
    • répétée

Control flow

  • Intimement lié à l'algèbre booléenne.

Goto : le mal incarné

  • Instruction goto ramène à la ligne déclarée dans l'instruction.
[Pseudo code, aucun langage]

1    print "Combien de bonjour vous voulez (1 ou 2) ?"
2
3    lire valeurLue
4
5    if valeurLue == 1:
6       goto 9
7
8    print "Bonjour"
9    print "Bonjour"
10

Control flow statements

  • Il existe plusieurs instructions de flux de commande.
[Pseudo code, aucun langage]

1    print "Combien de bonjour vous voulez (1 ou 2) ?"
2
3    lire valeurLue
4    
5    int compteur = 0
6    for( compteur != valeurLue, increment compteur )
7    {
8       print "Bonjour"
9    }
10

Nombreuses

  • Les instructions de flux de commande sont nombreuses :
===========
  do
      ...
  while bool_exp

===========
  while bool_exp
  do
      ...

===========
  if bool_exp
      ...
  else if bool_exp
      ...
  else
      ...

===========
  for( pre-processing ; bool_exp ; post-processing )
      ...

Expression vs instruction

  • Distinction significative dans ce contexte
int a
int b

...

if ( a + 3 > b - 6 ) print "vrai"

Expression seulement, pas instruction.

Bases du C++

Programmation procédurale

Procédurale

Forme de programmation où l'on décrit les opérations en séquences d'instructions exécutées par l'ordinateur pour modifier l'état du programme.

std::string myStrgVar;
std::string mySTRGVar; // OK, not the same

Bases

  • Langage compilé

 

  • Code sensible à la casse

 

  • Instructions se terminent par un point-virgule

 

  • Espaces blancs non significatifs

 

  • blocks délimités par des accolades
int        a    = 8;
int a = 8;  // exactly the same
void myFn( int    firstIn
           double secondIn );
if
{
    // statements
}
int    const    a;

Variable

type

  • POD
  • struct
  • classe
  • pointeur
  • référence

modificateur(s)

  • const
  • volatile
  • mutable
  • static

nom

int* a;
int *a;

Pointeur et référence

Variable de type pointeur:

  • Contient une adresse vers de la mémoire dans le heap.
  • Gestion manuelle.

Variable de type référence:

  • Deuxième nom pour une variable.
  • Ne peut jamais être non-initialisée (ne compilera pas).
  • Ne pas y penser comme un pointeur, ( même si c'est probablement implémenté comme ça).
int& b = a;
int &b = a;
    // illegal

Modèle de mémoire

  • Mémoire = RAM;

 

  • Divisée en sections :
    • heap → gérée par le programmeur
    • stack → LIFO, gérée par le compilateur

 

  • Les deux sont de la RAM, donc la même chose.

Bug fréquent en C++

  • Fuite de mémoire

 

  • Toujours le heap (jamais le stack) qui fuit:
    • variable sur le stack toujours libérée par le destructeur

Exécution d'un programme

int main( int argc, char* argv[] )
{
    int        var1;
    FirstType* var2;
    var2 = new FirstType;
    fn1()
    fn2()
    delete var2;
    return SUCCESS;
}
int fn1()
{
    SecondType  var1;
    SecondType* var2;
    var2 = new SecondType;
}
int fn2()
{
    ThirdType  var1;
    int        var2;
}

main()

int main( ... )
{
  int        var1;
  FirstType* var2;
  var2 = new FirstType;
  fn1()
  fn2()
  delete var2;
  return SUCCESS;
}

int var1;

int main( ... )
{
  int        var1;
  FirstType* var2;
  var2 = new FirstType;
  fn1()
  fn2()
  delete var2;
  return SUCCESS;
}

FirstType* var2;

int main( ... )
{
  int        var1;
  FirstType* var2;
  var2 = new FirstType;
  fn1()
  fn2()
  delete var2;
  return SUCCESS;
}

var2 = new FirstType;

int main( ... )
{
  int        var1;
  FirstType* var2;
  var2 = new FirstType;
  fn1()
  fn2()
  delete var2;
  return SUCCESS;
}

fn1();

int main( ... )
{
  int        var1;
  FirstType* var2;
  var2 = new FirstType;
  fn1()
  fn2()
  delete var2;
  return SUCCESS;
}

 [in fn1] SecondType var1;

int main( ... )
{
  int        var1;
  FirstType* var2;
  var2 = new FirstType;
  fn1()
  fn2()
  delete var2;
  return SUCCESS;
}
int fn1()
{
    SecondType  var1;
    SecondType* var2;
    var2 =
        new SecondType;
}

 [in fn1] SecondType* var2;

int fn1()
{
    SecondType  var1;
    SecondType* var2;
    var2 =
        new SecondType;
}
int main( ... )
{
  int        var1;
  FirstType* var2;
  var2 = new FirstType;
  fn1()
  fn2()
  delete var2;
  return SUCCESS;
}

 [in fn1] var2 = new SecondType;

int fn1()
{
    SecondType  var1;
    SecondType* var2;
    var2 =
        new SecondType;
}
int main( ... )
{
  int        var1;
  FirstType* var2;
  var2 = new FirstType;
  fn1()
  fn2()
  delete var2;
  return SUCCESS;
}

unwind stack fn1() : dtors

int main( ... )
{
  int        var1;
  FirstType* var2;
  var2 = new FirstType;
  fn1()
  fn2()
  delete var2;
  return SUCCESS;
}
int main( ... )
{
  int        var1;
  FirstType* var2;
  var2 = new FirstType;
  fn1()
  fn2()
  delete var2;
  return SUCCESS;
}

unwind stack fn1() : dtors

fn2();

int main( ... )
{
  int        var1;
  FirstType* var2;
  var2 = new FirstType;
  fn1()
  fn2()
  delete var2;
  return SUCCESS;
}

 [in fn2] ThirdType var1;

int main( ... )
{
  int        var1;
  FirstType* var2;
  var2 = new FirstType;
  fn1()
  fn2()
  delete var2;
  return SUCCESS;
}
int fn2()
{
    ThirdType  var1;
    int        var2;
}

 [in fn2] int var2;

int main( ... )
{
  int        var1;
  FirstType* var2;
  var2 = new FirstType;
  fn1()
  fn2()
  delete var2;
  return SUCCESS;
}
int fn2()
{
    ThirdType  var1;
    int        var2;
}
int main( ... )
{
  int        var1;
  FirstType* var2;
  var2 = new FirstType;
  fn1()
  fn2()
  delete var2;
  return SUCCESS;
}

unwind stack fn2() : dtors

int main( ... )
{
  int        var1;
  FirstType* var2;
  var2 = new FirstType;
  fn1()
  fn2()
  delete var2;
  return SUCCESS;
}

unwind stack fn2() : dtors

delete var2;

int main( ... )
{
  int        var1;
  FirstType* var2;
  var2 = new FirstType;
  fn1()
  fn2()
  delete var2;
  return SUCCESS;
}

unwind main() stack : dtors

int main( ... )
{
  int        var1;
  FirstType* var2;
  var2 = new FirstType;
  fn1()
  fn2()
  delete var2;
  return SUCCESS;
}

unwind main() stack : dtors

int main( ... )
{
  int        var1;
  FirstType* var2;
  var2 = new FirstType;
  fn1()
  fn2()
  delete var2;
  return SUCCESS;
}

Solution

  • Smart pointers :
    • std::unique_ptr< T >
      • Possession du pointeur
    • std::shared_ptr< T >
      • Pointeur partagé avec d'autre partie du programme

Déclarer une variable

  • Les variables sont sur le stack
  • Les pointeurs sont sur le stack
  • Ce vers quoi le pointeur pointe est :
int a( 4 );           // a is on the stack

int& b( a );          // b is on the stack because it's a

int* c( new int );    // c is on the stack.

*c = 4;               // what c points to is on the heap
                      // so 0xf6a340bc is on the stack
                      // and 4 is on the heap

int* d( &a )          // d is on the stack and points to
                      // the stack
  • stack
  • heap
int a( 4 );         // equivalent to int a = 4;
int& b = a;
int* c = new int;
*c = 4;

printf( "%i", a );   // outputs 4 to the screen
printf( "%i", b );   // outputs 4 to the screen
printf( "%i", c );   // outputs address of c to the screen
printf( "%i", *c );  // outputs 4 to the screen

Accéder aux valeurs

Variable et référence:

  • le nom simplement

Pointeur

  • déréférencer d'abord avec l'opérateur *

const

  • Compilation et communication seulement
  • Sans effet à l'exécution

Compilation

Communication

Dis au compilateur de ne pas permettre de modification de la variable.

Promet à l'utilisateur du code que la variable ne changera pas après l'appel de fonction.

Erreurs de compilation

ptrs et refs : attention

Lire de droite à gauche

//*** Read from right to left ***

int const* a;        // pointer to const int
                     // pointer can change, pointee can't

int* const a;        // const pointer to int
                     // pointer can't change, pointee can

int const* const a;  // const pointer to const int
                     // pointer and pointee can't change
double  convertToDbl( std::string const& strg );

Fonction : déclaration

sortie

entrant(s)

  • peut être vide
  • peut être modifiable
    • référence ou ~pointeur
  • peut être invariant
    • const ou valeur

nom

#include <string>

double convertToDbl( std::string const& strg )
{
    int a;                // accessible only in the function body
    strg = "nouvelle valeur";   // forbidden because of the const
    double convertedVal = atod( strg.c_str() );
    return convertedVal;
}

Fonction : définition

Dans le scope de la fonction, seules les variables globales, définies localement et d'entrées sont disponibles.

double  convertToDbl( std::string const& strg );

pass by...

Les entrants d'une fonction peuvent l`être de quelques façons différentes :

  • par valeur
  • par référence
  • par pointeur

En réalité, tout est passé par copie...

double  convertToDbl( std::string strg );

pass by value

  • Une copie est passée dans la fonction.

 

  • Seule la copie est disponible.

 

  • Impossible de modifier la variable originale.

 

  • Fonction dite sans effet secondaire (sauf si modification d'une variable globale (déconseillé).
double  convertToDbl( std::string& strg );

pass by (non-const) reference

  • Une copie de la référence est passée dans la fonction.
    • Nouvelle référence à la variable originale.

 

  • Variable originale est modifiable.
double  convertToDbl( std::string* strg );

pass by pointer (to non-const)

  • Une copie du pointeur est passée dans la fonction.
    • Nouveau pointeur vers la même mémoire.

 

  • Variable originale est modifiable.
  • Pointeur original non modifiable (copie).

pass by reference

Si fonction...

  • Modifie son argument
    • référence
  • ne modifie pas son argument
    • référence const
void fn1( std::string& willBeModified )
{}

std::vector< double >
fn2( std::string const& willNotBeModified )
{}
  • Beaucoup plus rapide que par valeur.
  • Considéré plus sécuritaire que les pointeurs.
struct Staff
{
   int id
   char* lastName;
   char* firstName;
   char* jobTitle;
};

Struct C (pas ++)

  • Façon de regrouper des données.

 

  • Plus facile à utiliser comme entrant dans les fonctions.
void fn_A( int staffID, char* lastName, char* firstName, char* jobTitle );

void fn_B( Staff& staff );
include directive object pointer reference function declaration function definition
#include <string>                        // (1)
#include <iostream>                      // (1)

int a;                                   // (2)
int* b;                                  // (3)
int& c;                                  // (4)

double fn( std::string const& nbStrg );  // (5)

double fn( std::string const& nbStrg )
{
    double converted;
    ...                                  // (6)
    return converted;
}

Vocabulaire de base

Orienté objet en C++

Orientée objet

Forme de programmation où l'on génère des objets (données + routines) pour représenter les concept du programme.

 

L'interaction entre ces objets, via leurs relations, permet de concevoir et réaliser les fonctionnalités attendues et de résoudre le ou les problèmes.

Pas uniquement OO

Contrairement à ce qui est souvent pensé, le C++ n'est pas un langage orienté objet.  Il permet ce paradigme, mais ne l'impose pas.

Code partagé

  • Bibliothèque de code partagé avec un collègue qui contient (entre autre) ce qui suit :
struct Staff
{
   int id
   char* lastName;
   char* firstName;
   char* jobTitle;
};

struct StaffVector
{
   Staff* allStaff[];
   int nbInVec;
};
void addStaff( StaffVector& vec, Staff const& staffToAdd )
{
   // Add staffToAdd
   ++(vec.nbInVec);
}


void removeStaff( StaffVector& staffVec, int idx )
{
   // Remove staff at index idx
   --(vec.nbInVec);
}

Cohérence

  • Collègue décide que son implémentation est mieux.
void addStaff( StaffVector& vec, Staff const& staffToAdd )
{
   // Add staffToAdd
   ++(vec.nbInVec);
}


void removeStaff_Better( StaffVector& vec, int idx )
{
   // Remove staff at index idx
   // Oups, forgot to decrement
}

Cohérence de la structure StaffVector est perdue

  • Solution : encapsulation

Classe

  • Regroupement:
    • données privées
    • méthodes publiques

 

  • Doit représenter un concept.

 

  • Plusieurs avantages autres que celui utilisé en exemple.
struct Staff
{
   ...
};

class StaffVector
{
public:
   add( Staff const& staff );
   remove( int idx );

private:
   Staff* allStaff_[];
   int nbInVec_;
};

Droits d'accès

  • public:
    • accès par tous
  • private:
    • accès par les fns membres
class StaffVector
{
public:
   add( Staff const& staff );
   remove( int idx );

   int myMemberPublicData;

private:
   Staff* allStaff_[];
   int nbInVec_;
};
void fn1( Staff& staff )
{
    staff.nbInVec_ = 5;            // compile no, priv. and non-member fn
    staff.myMemberPublicData = 8;  // compile ok, bad practice
}

Ne compile pas

  • nbInVec plus accessible :
    • Plus possible d'implémenter à l'extérieur de la classe
    • Tentative d'accès ne compilera pas

 

  • Autres avantages :
    • Destructeurs et constructeurs évitent l'oubli de libérer la mémoire
    • Objet auto passé à la fonction lors de l'appel

Pas une struct

  • Une struct classique (en C) :
    • pas de méthode associée.
    • accès  non restreint.
class StaffVector
{
public:
   add( Staff const& staff );
   remove( int idx );

private:
   Staff* allStaff_[];
   int nbInVec_;
};
struct StaffVector
{
   Staff* allStaff[];
   int nbInVec;
};

Mais en C++...

class StaffVector
{
public:
   add( Staff const& staff );
   remove( int idx );

private:
   Staff* allStaff_[];
   int nbInVec_;
};
struct StaffVector
{
public:
   add( Staff const& staff );
   remove( int idx );

private:
   Staff* allStaff_[];
   int nbInVec_;
};

Équivalent, sauf pour l'accès par défaut, c.-à-d. sans spécification.

Différence

struct StaffVector
{
   add( Staff const& staff );
   remove( int idx );

   Staff* allStaff_[];
   int nbInVec_;
};
class StaffVector
{
public:
   add( Staff const& staff );
   remove( int idx );

   Staff* allStaff_[];
   int nbInVec_;
};
struct StaffVector
{
private:
   add( Staff const& staff );
   remove( int idx );

   Staff* allStaff_[];
   int nbInVec_;
};
class StaffVector
{
   add( Staff const& staff );
   remove( int idx );

   Staff* allStaff_[];
   int nbInVec_;
};

=

=

Objet

  • Représente une instance d'un type.

 

  • Nom habituellement réservé à:
    • struct
    • classes

 

  • Représenté par un nom de variable

Classe

Recette pour créer un type d'objet.

Objet

Résultat de l'application de la recette pour créer un objet spécifique.

#include <string>

class Staff
{
   int id_;
   std::string lastName_;
   std::string firstName_;
   std::string jobTitle_;
};
#include "Staff.h"

// Instances
// Object 1
Staff staff1( 0, "Leclerc",
              "Ghyslain", "Phys" );

// Object 2
Staff staff2( 1, "Brouard",
              "Lucie", "Tech" );

Staff.h

Privée par défaut

const, prise 2

  • Autre spécification const valide
class StaffVector
{
public:
   void remove( int idx );
   int size() const;

private:
   Staff* allStaff_[];
   int nbInVec_;
};

Indique si une fonction membre peut ou pas être utilisée sur un objet const.

StaffVector       staffVec1;
StaffVector const staffVec2;

staffVec1.size()     // Compile ok, non-const object, const fn
staffVec2.size()     // Compile ok, const object, const fn 

staffVec1.remove()   // Compile ok, non-const object, non-const fn
staffVec2.remove()   // Compile no, const object, non-const fn 

Anatomie fonctionnelle d'une classe

  • Constructeurs
  • Destructeur
  • Fonctions d'accès (getters)
  • Fonction d'assignation (setters)
  • Opérateurs
  • Fonctions variées

Constructeurs

#include <string>

class Staff
{
public:
   Staff();                            // Default ctor : called when no args
   Staff( std::string lastName,
          std::string firstName );     // Nondescript ctor
   Staff( Staff const& staff );        // Copy ctor : creates a duplicate

private:
   int id_;
   std::string lastName_;
   std::string firstName_;
   std::string jobTitle_;
};
  • Appelé lorsque l'on crée un objet.

Destructeur

#include <string>

class Staff
{
public:
   Staff();
   Staff( Staff const& staff );
   ~Staff();                           // dtor

private:
   int id_;
   std::string lastName_;
   std::string firstName_;
   std::string jobTitle_;
};
  • Appelé lorsque l'objet sort du scope (p.ex. : fin d'une fonction).

Accès/assignation

#include <string>

class Staff
{
public:
   int id() { return id_; }                  // Getter, access value of id_
   void setId( int newId ) { id_ = newId; }  // Setter, change value of id_

private:
   int id_;
   std::string lastName_;
   std::string firstName_;
   std::string jobTitle_;
};
  • Brise l'encapsulation si toutes les variables en ont.

Overload d'opérateurs

#include <string>

class Staff
{
public:
   Staff& operator=( Staff const& staff );         // Assignment operator
   void operator++() { jobTitle_ = "Président"; }  // increment operator

private:
   int id_;
   std::string lastName_;
   std::string firstName_;
   std::string jobTitle_;
};
  • Utiliser avec jugement.
  • Généralement recommandé de réserver pour les types mathématiques.

Fonctions membres

#include <string>

class Staff
{
public:
   // better than increment operator
   void promote() { jobTitle_ = "Président"; }

private:
   int id_;
   std::string lastName_;
   std::string firstName_;
   std::string jobTitle_;

   void myPrivateFunc_();
};
  • Réserver le plus possible à ce qui ne peut pas être efficacement défini à l'extérieur de la classe.

Fonctions non-membres

#include <string>

class Staff
{
   ...
};

void fn( Staff& staff );
  • Utilise uniquement les fonctions publiques de la classe.
  • Plus robuste aux changements d'implémentation.

Syntaxe d'appel

// Let a class Staff exist and be defined in Staff.h

#include "Staff.h"

Staff staff1;                 // call ctor by default
Staff staff2();               // call ctor by default

Staff* staff3 = new Staff();  // call ctor by default

staff1.fn1();                 // call fn1 on object staff1
staff3->fn1();                // call fn1 on object staff3

fn2( staff2 );                // call non-member function fn2 on staff2

-(*staff3);                   // call unary operator - on staff3
staff1 + staff2;              // call binary operator +

Définitions par défaut

Le compilateur génère quelques fonctions par défaut si non écrites [C++03] :

  • Constructeur par défaut
  • Constructeur de copie
  • Assignation
  • Destructeur
class A
{
    A();
    A( A const& a );
    A& operator=( A const& a );
    ~A();
};

Définitions par défaut

  • Le C++11 en ajoute 2 (version move, dépasse le cadre de cette présentation) et permet de demander explicitement celle du compilateur.
class A
{
    A() = default;
    A( A const& a ) = default;
    A& operator=( A const& a ) = default;
    ~A() = default;
};

Polymorphisme

  • Overloading

  • Héritage

  • Templates

Polymorphisme

  • L'idée de permettre à un même code d'être utilisé avec différents types.
    • ad hoc
    • sous-typage (héritage)
    • paramétrique

Celui auquel le mot fait généralement référence

ad hoc

  • function overloading
int fn1( int input1 );
int fn1( double input1 );
int fn1( std::string input1 );

// illegal, no overloading on return value
double fn1( int input1 );
  • utilisation de fn1 avec plus d'un type :
    • polymorphisme
  • disponible dans tous les paradigmes
  • nécessite la définition d'une fonction par type

sous-typage (héritage)

  • Utiliser une classe de base pour en définir une seconde.
    • Forme de réutilisation.
class ObjectThanCanFly
{
   virtual void fly();
   virtual double currentAltitude() const;
};

class Bird : public ObjectThanCanFly
{
    // gets functions fly() and currentAltitude() whitout defining them
};

class Plane : public ObjectThanCanFly
{
    // gets functions fly() and currentAltitude() whitout defining them
};

Pointeur/référence générique

void make_fly( ObjectThatCanFly& obj )
{
   obj.fly();
}

int main( int argc, char* argv[] )
{
   Bird theBird;
   Plane thePlane;

   make_fly( theBird );
   make_fly( thePlane );
}
  • utilisation de make_fly avec plus d'un type :
    • polymorphisme

 

  • fonction définie une fois.

 

  • redéfinie au besoin dans les classes dérivées.

Exécution (runtime)

  • Exécution pour une classe dérivée:
  • appel méthode pour aller chercher l'adresse de la fonction dans l'objet
  • appeler la fonction
  • Exécution pour une classe non virtuelle (sans héritage) :
  • appeler la fonction

Significatif ? Si appellé 1 000 000 de fois, peut-être. Vérifier au besoin.

Paramétrique : template

  • Écrire du code qui écrit du code.
template< typename T >
void swap( T& left, T& right )
{
   T temp( left );
   left = right;
   right = temp;
}


int a( 6 ), b( 7 );
double c( 4.5 ), d( -5.7 );

swap( a, b );  // create with T == int
swap( c, d );  // create with T == double
  • Recette générique paramétrée par le type.

 

  • Utilisation de swap avec plusieurs type:
    • polymorphisme

Dans l'exemple

void swap( int& left, int& right )
{
   int temp( left );
   left = right;
   right = temp;
}
void swap( double& left, double& right )
{
   double temp( left );
   left = right;
   right = temp;
}

Compilation (compile-time)

  • Contrairement au polymorphisme par sous-typage, il n'y a aucun coût à l'exécution.

 

  • Le coût est à la compilation.
    • Fonction générée seulement lorsque demandée.
    • Doit être créée pour
      • chaque unité de compilation
      • chaque type

Paradigme générique

Définition d'algorithmes identiques opérant sur des données de types différents spécifiés plus ultérieurement.

Collisions et namespaces

Bonne pratique

  • Très utilisé dans les grandes librairies
    • Boost
    • ITK
    • VTK

Collisions de nom

  • Supposons que l'on veuille utiliser deux librairies externes pour un projet.

 

  • Supposons maintenant que ces deux librairies ont décidé de se faire une classe String pour les chaines de caractère plutôt que d'utiliser les pointeurs de char (une excellente décision).

Collisions de nom

  • Problème lorsque l'on importe les deux librairies pour une même unité de compilation.
#include "lib1/string.h"
#include "lib2/string.h"

void myCoolFunction()
{
    string nameOfUser( "" );  // which string -> name clash
}

Solution

#include "lib1/string.h"
#include "lib2/string.h"

void myCoolFunction()
{
   lib1::string nameOfUser( "" );  // using string class of lib1
}

main.cxx

namespace lib1
{
class string{};
} // namespace lib1
namespace lib2
{
class string{};
} // namespace lib2

lib1/string.h

lib2/string.h

  • Regroupement de classes et de fonctions reliées
    • Outil d'organisation du code
    • Imbricables
    • Additifs

namespace

namespace blabala
{
   void fn1();
   namespace inner  // can be nested
   {
      void fn2();
   } // namespace inner
} // namespace blabala


namespace blabala  // are additive (as opposed to classes and structs)
{
   void fn3();
} // namespace blabala
  • Pour les classes, l'opérateur d'appel était le '.'.

 

  • Pour le namespace, c'est l'opérateur de scope '::'.

Syntaxe d'appel

// Different calls for fn1.

::fn1();       // At global scope.  If not found, error.

::std::fn1();  // From the namespace std at the global scope.  If not found, error.

std::fn1();    // From the namespace std at current scope.
               // If not found, try to find namespace std in global scope
               // and fn1 in that one.
               // If not found, error.

Processus de compilation

  • Fichier unique

  • Fichiers multiples

  • Avec libraire(s) externe(s)

Une fois écrit...

  • Une fois le code écrit, il faut en faire un exécutable.

 

  • Le processus se nomme compilation.

 

  • Compilateurs disponibles:
    • Windows : MSVC ou MinGW
    • OS X / Linux : GCC ou Clang

Fichier unique

computer > g++   -o nom_executable   main.cpp 

Fichiers que l'on écrit

Compilateur

Options du compilateur

Fichier unique

computer > cl.exe   /EHsc   main.cxx

Étapes de compilation

Deux étapes :

  • Préprocesseur (macros)
  • Compilation (C++)

Préprocesseur

  • Applique toutes les macros
    • #include
    • #define
    • #ifndef

 

  • Copier - coller

 

  • Produit des fichiers ASCII temporaires de C++

Compilateur

  • Transforme les fichiers ASCII de précompilation en série d'instructions pour le processeur de l'ordinateur

 

  • Produit des fichiers binaires

Problématique

  • Impossible de réutiliser le code autre que par copier-coller

 

  • Fichier devient trop gros
    • Kernel Linux : ~10 000 000 lignes de code
    • Office (2003) : ~ 30 000 000 lignes de code
    • Windows 7 : ~ 40 000 000 lignes de code

Fichiers multiples

C'est ici que l'on code

Étapes de compilation

  • Compilation séparée de chaque fichier
    • Concept d' unité de compilation
    • produit un fichier objet
      • .o → binaire du fichier .cxx correspondant

 

  • .exe  → créé par le linker à partir des .o

Fastidieux

computer > g++ -c file1.cxx
computer > g++ -c file2.cxx
...
computer > g++ -c main.cxx

computer > g++ -o nom_executable file1.o file2.o ... main.o

Raccourci pratique pour appeler le compilateur et l'éditeur de lien

Projets

  • Pratique
    • Une seule commande lance la compilation.

 

  • Recompilation...
    • efficace → uniquement ce qui a changé
    • sécuritaire → ce qui a changé sans oubli

Logiciels disponibles

  • GNU make (Linux, OS X)
    • Fichiers texte

 

  • Visual Studio (Windows)
    • GUI

 

  • XCode (OS X)
    • GUI

Simplement

  • Liste d'instructions pour compiler un programme

 

  • Façon varie grandement d'un logiciel à l'autre
    • GNU make a sa syntaxe avec "targets"
    • GUI utilise généralement un ensemble de fichiers binaires et de fichiers XML.

 

  • Possible d'unifier avec CMake (pour un autre jour...)

Avec librairie externe

Fichiers multiples v2

  • Compilateur doit :
    • avoir les fichiers entête (.h) dans ses chemins par défaut
    • recevoir leur emplacement en entrée 

 

  • Éditeur de lien doit :
    • avoir les fichiers binaires (.lib) dans ses chemins par défaut
    • recevoir leur emplacement en entrée

Fichiers multiples v2

  • Autrement, c'est sensiblement la même chose que la compilation de fichiers multiples.

 

  • La spécification des chemins se fait généralement via le projet (make, MSVC, XCode, etc.).

Conseils pratiques en C++

Fichiers

  • Trois types principaux
  • Header
    • Déclarations
  • Inline
    • Définitions inline ou template
  • Source
    • Définitions

Header

  • Déclarations seulement