#include <iostream>

int main ()
{
  std::cout << "Bienvenue au "
               "cours du GULL "
               "sur C++ !\n";
}

TOC

  1. C++

Historique

1972 Dennis Ritchie invente C aux Bell Labs.
1979 Bjarne Stroustrup invente C with Classes aux Bell Labs, inspiré par Simula67.
1983 C with Classes est renommé en C++.
1990 The Annotated C++ Reference Manual (ARM), souvent cité en référence, paraît.
1991 The C++ Programming Language Second Edition, avec lequel de nombreuses personnes (dont moi-même) ont appris C++, paraît.
1997 The C++ Programming Language Third Edition paraît.
1998 Le standard ISO C++ (ISO/IEC 14882:1998) est adopté.
2003 Le technical corrigendum 1 (TC1) au standard (ISO/IEC 14882:2003) est adopté.
200x Une nouvelle version de C++ (C++-0x) est prévue.

Design de C++

C++ :

Utilisation

Mots clé de C++

Mot clé de C ; mot clé de C++ ; mot clé plus récent de C++
Contrôle for do while break continue switch case default if else goto return
Types
de base char short int long signed unsigned float double void bool wchar_t
définis struct union enum typedef
stockage auto static extern const volatile register
valeurs false true
conversion static_cast const_cast reinterpret_cast
RTTI dynamic_cast typeid
Opérateurs sizeof new delete and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq
Namespaces namespace using
Exceptions try throw catch
Classes class this private protected public virtual friend operator explicit mutable
Templates template typename export
Bas niveau inline asm

Pas des mots clé de C++

autre langageéquivalent C++
array int t [80];
function double sin (double);
procedure void perror (const char *);
import #include (commande du préprocesseur)
null, NULL 0
final C'est final par défaut, utiliser virtual autrement
finally Classe::~Classe() { detruire(); }
set std::set<int> ensemble;
in ensemble.find(element) != ensemble.end()
foreach std::for_each(coll.begin(), coll.end(), faire);
interface, abstract class Garde {
public:
virtual void Alerter() = 0;
};
extends, implements class Chien : public Animal, public Garde {
};
super typedef Base super;

Hello world : Aperçu de la syntaxe

#include <iostream>
int main ()
{
  std::cout << "Hello, new world!\n";
}

Hello world : évolution ARM - ISO C++

#include <iostream.h>
int main ()
{
  cout << "Hello, new world!\n";
  return 0;
}
Du temps de l'ARM : Cet exemple est obsolète mais reste compatible grâce au iostream.h actuel :
#include <iostream>
using namespace std;

Types de base

bool b             = true;
char c             = 'A';
wchar_t wc         = L'A';
signed char sbyte  = -128;
unsigned char byte = 255;
short s            = 32767;
unsigned short us  = 22;
int i              = 32;
unsigned u         = 65535u;
long l             = 123456789L;
unsigned long ul   = 4294967295UL;
float f            = 1.23f;
double g           = 1324e-53;
long double h      = 1324e156L;
void F ();

Définition d'objets (de variables)

int global; // définition d'un objet global de type int

void f ()   // définition d'une fonction globale
{
  int local;       // objet local non initialisé
  int local2 = 2;  // initialisé avec la syntaxe C
  int local3 (3);  // initialisé avec la syntaxe C++
  ObjetComplique obj("hello", 132, '0'); // syntaxe C++ avec
                                         // plusieurs paramètres
  int locale ();   // déclaration de fonction !
  local = 3;                 // instruction
  AutreObjet apres = local;  // définition après instruction
  for (int i = 0; i < 10; ++i) { /* ... */ }
  // destruction automatique des objets locaux
}

Modificateurs de stockage

#include <iostream>

int i = 10;  // définition de i
void f ();   // déclaration de f

int main () { f(); f(); }

void f () {  // définition de f
  extern int i;      // déclaration (définition ci-dessus)
  static int j = 0;  // objet statique, survit au bloc
  int k = 0;         // objet local
  const int c = 0;   // objet local constant
  std::cout<<++i<<' '<<++j<<' '<<++k<<' '<<++c<<'\n';
  std::cout<<++i<<' '<<++j<<' '<<++k<<' '<<++c<<".\n";
}
/tmp> ./a.out
11 1 1 0
12 2 2 0.
13 3 1 0
14 4 2 0.

Opérateurs

opérateurs de C/C++
., ->, [], (), ++, --, sizeof, ~, !, -, +, &, *, /, %, <<, >>, <, <=, >, >=, ==, !=, ^, |, &&, ||, =, *=, /=, %=, %=, +=, -=, <<=, >>=, &=, |=, ^=, ?:, ,
opérateurs de C++
::, typeid, dynamic_cast, static_cast, reinterpret_cast, const_cast, new, new[], delete, delete[], .*, ->*, throw
opérateurs surchargeables en C++
+, -, *, /, %, ^, &, |, ~, !, =, <, >, +=, -=, *=, /=, %=, ^=, &=, |=, <<, >>, >>=, <<=, ==, !=, <=, >=, &&, ||, ++, --, ->*, ,, ->, [], (), new, new[], delete, delete[]

Valeur, référence et pointeur

#include <iostream>
int main () {
  int   i = 1;   // valeur entière
  int & r0;      // une référence doit être initialisée
  int & r = i;   // référence à une valeur entière
  int * p0;      // pointeur non initialisé
  int * p = i;   // erreur, types incompatibles
  int * p = &i;  // pointeur sur une valeur entière
  std::cout<< i <<' '<< r <<' '<<  p <<'\n'
           << i <<' '<< r <<' '<< *p <<'\n';
  ++r;  // incrémente i à travers r
  ++p;  // incrémente p qui pointe à côté de i ! ++*p est OK.
  std::cout<< i <<' '<< r <<' '<<  p <<'\n'
           << i <<' '<< r <<' '<< *p <<'\n';
}
/tmp> ./a.out
1 1 0xbffff914
1 1 1
2 2 0xbffff918
2 2 -1073743544

Types définis

enum Couleur { BLANC, ROUGE = 0xF00, VERT = 0x0F0, BLEU };
Couleur c = VERT;  // défini une variable de type Couleur

struct Fraction {
  int numerateur;
  int diviseur;
};
Fraction f = { 1, 2 };  // défini une variable de type Fraction

int main () {
  f . numerateur = 2 ;    // accès au membre
  Fraction * pf = & f ;   // pointeur sur Fraction
  pf -> diviseur = 3 ;    // équivalent à (*pf).diviseur
  // pointeur sur membre de Fraction de type int
  int Fraction :: * pm = & Fraction :: numerateur ;
  f .* pm = 4 ;
  pf ->* pm = 5 ;
}

Passage de paramètres

#include <iostream>
void f1 (int   i) {  i = 1; } // par valeur
void f2 (int & i) {  i = 2; } // par référence
void f3 (int * i) { *i = 3; } // par pointeur
int main ()
{
  int i[] = { 0, 0, 0 };
               f1 (i[0]);  f2 (i[1]); f3 (&i[2]);
  std::cout<<"{ "<<i[0]<<", "<<i[1]<<", "<<i[2]<<" }\n";
  f1 (0);
  f2 (0);  // interdit
  f3 (0);  // pointeur nul, plante f3() !
}
/tmp> ./a.out
{ 0, 2, 3 }
Erreur de segmentation

Allocation de mémoire : new et delete

#include <memory>
#include <string>
int * f () {
  int * pi1 = new int;  // simple allocation de mémoire
  int * pi2 = new int (2);  // initialise
  std::string * pi3 = new std::string("leak");  // construit
  std::auto_ptr<int> pi4 (new int(4));
  delete pi1;  // libère *pi1
  return pi2;  // *pi2 reste pointé par p dans main
  // *pi3 n'est plus pointé => mémoire perdue (fuite, leak)
  // *pi4 est détruit par le destructeur de pi4.
}
int main () {
  int * p = f ();
  delete p;
}

Compilation

/tmp> cat >hello.cc
#include <iostream>

int main ()
{
  std::cout << "Hello, new world!\n";
}
/tmp> g++ hello.cc
/tmp> ls -l a.out
-rwxrwxr-x  1 marc marc 13527 2006-01-12 23:52 a.out
/tmp> ./a.out
Hello, new world!
/tmp> 

Erreurs de compilation

#include <iostream>

int main ()
{
  std::cout << "Hello, new world!\n"
}
/tmp> g++ hello.cc
hello.cc: In function `int main()':
hello.cc:6: erreur: expected `;' avant un élément lexical « } »
/tmp> 
int main ()
{
  std::cout << "Hello, new world!\n";
}
/tmp> g++ hello.cc
hello.cc: In function `int main()':
hello.cc:3: erreur: « cout » n'est pas un membre de « std »
/tmp> 

Erreurs de compilation, suite

Appeler gcc au lieu de g++

/tmp> gcc hello.cc
/tmp/ccsbrTRT.o(.text+0xd): In function `std::__verify_grouping(char const*, unsigned int, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)':
: undefined reference to `std::basic_string<char, std::char_traits<char>, std::allocator<char> >::size() const'
/tmp/ccsbrTRT.o(.text+0x60): In function `std::__verify_grouping(char const*, unsigned int, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)':
: undefined reference to `std::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator[](unsigned int) const'
/tmp/ccsbrTRT.o(.text+0x9f): In function `std::__verify_grouping(char const*, unsigned int, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)':
: undefined reference to `std::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator[](unsigned int) const'
/tmp/ccsbrTRT.o(.text+0xce): In function `std::__verify_grouping(char const*, unsigned int, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)':
: undefined reference to `std::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator[](unsigned int) const'
/tmp/ccsbrTRT.o(.text+0x127): In function `main':
: undefined reference to `std::cout'
/tmp/ccsbrTRT.o(.text+0x12c): In function `main':
: undefined reference to `std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)'
/tmp/ccsbrTRT.o(.text+0x155): In function `__static_initialization_and_destruction_0(int, int)':
: undefined reference to `std::ios_base::Init::Init()'
/tmp/ccsbrTRT.o(.text+0x170): In function `__static_initialization_and_destruction_0(int, int)':
: undefined reference to `std::ios_base::Init::~Init()'
/tmp/ccsbrTRT.o(.eh_frame+0x11): undefined reference to `__gxx_personality_v0'
collect2: ld a retourné 1 code d'état d'exécution
/tmp> 

Étapes de compilation

étapeoutilcommande en lignerésultat
1Préprocesseurcppg++ -E hello.ccstdout
2Compilationcc1plusg++ -S hello.cchello.s
3Assemblageasg++ -c hello.cc ou g++ -c hello.shello.o
4Édition des liensldg++ hello.cc ou g++ hello.o ou g++ hello.sa.out
Autres options courantes en ligne de commande
exemple de compilation
g++ -c hello.cc -DLINUX -I../mylibs/include -Wall -g -O -ansi
exemple d'édition des liens
g++ hello.o -L../mylibs -lmy -o hello

Sortie du préprocesseur

/tmp> gcc -E hello.cc >hello.E
/tmp> ls -l hello.E
-rw-rw-r--  1 marc marc 716014 2006-01-23 06:30 hello.E
/tmp> less hello.E

Sortie du compilateur

/tmp> g++ -S hello.cc
/tmp> cat hello.s

Sortie de l'assembleur

/tmp> gcc -c hello.cc
/tmp> nm hello.o --demangle
00000044 t global destructors keyed to main
0000005c t global constructors keyed to main
         U __gxx_personality_v0
00000074 T main
00000000 t __static_initialization_and_destruction_0(int, int)
         U std::ios_base::Init::Init()
         U std::ios_base::Init::~Init()
         U std::cout
00000000 b std::__ioinit
         U std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)

Projet avec plusieurs fichiers sources

Fichiers inclus contenant les déclarations iostream c.h
// fichier standard
// distribué avec GCC
#ifndef C_H
#define C_H
char b(void);
char id(char);
#endif
fichiers source contenant les définitions (l'implémentation) main.cc c.cc
#include <iostream>
#include "c.h"

int main () {
  std::cout<<b()<<id('a')<<"r\n";
}
#include "c.h"

char b(void)
{ return 'b'; }

char id(char c)
{ return c; }

Compilations et édition des liens (linking)

/tmp> g++ -c c.cc
/tmp> g++ -c main.cc
/tmp> ls -l *.o
-rw-rw-r--  1 marc marc  691 2006-01-13 00:32 c.o
-rw-rw-r--  1 marc marc 2688 2006-01-13 00:32 main.o
/tmp> g++ main.o c.o -o foo
/tmp> ls -l foo
-rwxrwxr-x  1 marc marc 13970 2006-01-13 15:20 foo
/tmp> ./foo
bar
/tmp>

Mélange C/C++

c.cc.hmain.cc
#include "c.h"

char b(void)
{
  return 'b';
}

char id(char c)
{
  return c;
}
#ifndef C_H
 #define C_H
 #ifdef __cplusplus
  extern "C" {
 #endif
 char b(void);
 char id(char);
 #ifdef __cplusplus
  }
 #endif
#endif
#include <iostream>
#include "c.h"

int main()
{
  std::cout << b()
            << id('a')
            << 'r'
            << std::endl;
}
/tmp> gcc -c c.c
/tmp> g++ -c main.cc
/tmp> ls -l *.o
-rw-rw-r--  1 marc marc  682 2006-01-13 00:32 c.o
-rw-rw-r--  1 marc marc 2680 2006-01-13 00:32 main.o
/tmp> g++ main.o c.o -o foo
/tmp> ./foo
bar

Différence entre fichiers objet C/C++ : name mangling

/* c.c */
char b (void) { return 'b'; }
// cc.cc
char b(void);  // déclaration sans extern "C"
int main () { b(); }
/tmp> gcc -c c.c
/tmp> nm c.o
00000000 T b
/tmp> g++ -c cc.cc
/tmp> nm cc.o
         U __gxx_personality_v0
00000000 T main
         U _Z1bv
/tmp> g++ c.o cc.o
cc.o(.text+0x12): In function `main':
: undefined reference to `b()'
collect2: ld a retourné 1 code d'état d'exécution

C++ ABI (Application Binary Interface)

Création de types

« Le but du concept de classe de C++ est de fournir au programmeur un outil pour créer de nouveaux types qui peuvent être utilisés aussi aisément que les types prédéfinis.» (Stroustrup)

Exemple de tels types : std::string et std::ostream.

#include <string>
#include <iostream>

int main ()
{
  std::string h = "hello";
  std::string w = "world";
  h += ", ";        // Appond une chaîne avec +=
  w = h + w + '\n'; // Concaténation avec +, assignation avec =
  std::cout << w;   // Affichage du tout avec <<
}
/tmp> ./a.out
hello, world

Type concret

Création d'un type Fraction (1/9)

#include <iostream>
#include "fraction.h" // contient la déclaration de Fraction
int main () {
  Fraction f1 (6, 8); // = 6/8 = 3/4
  Fraction f2 = 2;    // = 2/1
  Fraction f3;        // non initialisé
  Fraction f4 = f1;   // = f1
  f3 = f1;
  f3 = Fraction(1, 4) + f3;
  f2 = f2 * f1 + 1;
  std::cout<<"f1 = "<<f1<<"\nf2 = "<<f2<<"\nf3 = "<<f3<<'\n';
}
/tmp> ./a.out
f1 = 3/4
f2 = 5/2
f3 = 1

Fraction : Structure (2/9)

#include <iostream>
struct Fraction {
  int numerateur;
  int diviseur;
};
int main () {
  Fraction f1 = {6, 8}; // initialisation
  Fraction f2 = {2};    // diviseur non initialisé !
  Fraction f3;          // non initialisation OK
  Fraction f4 = f1;     // initialisation avec une fraction OK
  f3 = f1;              // assignation d'une fraction OK
  std::cout<<f1.numerateur<<'/'<<f1.diviseur<<'\n';
}
/tmp> ./a.out
6/8

Fraction : ajout d'une méthode (3/9)

#include <iostream>

struct Fraction {
  int numerateur, diviseur;
  void reduire ();
};

int main ()
{
  Fraction f1 = {6, 8}; // initialisation
  f1.reduire();         // transforme 6/8 en 3/4
  std::cout<<f1.numerateur<<'/'<<f1.diviseur<<'\n';
}
/tmp> ./a.out
3/4

Fraction : implémentation de la méthode (4/9)

struct Fraction {
  int numerateur, diviseur;
  void reduire ();
};

void Fraction::reduire ()
{
  if (diviseur < 0) {
    numerateur = -numerateur;
    diviseur = -diviseur;
  }
  int a = std::abs(numerateur), b = diviseur, c;
  while (b != 0) {
    c = a % b;
    a = b;
    b = c;
  }
  numerateur /= a;
  diviseur /= a;
}

this

// méthode de Fraction
void Fraction::fun () {
        numerateur = 0;  // équivalent
  this->numerateur = 0;  // équivalent
}

// fonction agissant sur une Fraction
void fun (Fraction * This) {
  This->numerateur = 0;
}

void f () {
  Fraction f;
  f.fun();   // appel de méthode
  fun(&f);   // appel de fonction
}

Fraction : ajout d'un constructeur (5/9)

struct Fraction {
  int numerateur, diviseur;
  Fraction(int, int);
  void reduire();
};
Fraction::Fraction(int n, int d)
  : numerateur (n), diviseur (d) { reduire(); }
int main () {
  Fraction f1 (3, 4);  // construction avec 2 entiers OK
  Fraction f2 (2);     // construction avec 1 entier
  Fraction f2 = {2, 1};// initialisation littérale
  Fraction f3;         // pas d'initialisation
  Fraction f4 = f1;    // construction par copie OK
  f3 = f1;             // assignation d'une fraction OK
}
Le constructeur :

Fraction : surcharge du constructeur (6/9)

struct Fraction {
  int numerateur, diviseur;
  Fraction(int n, int d): numerateur (n), diviseur (d) { reduire(); }
  Fraction(int n)       : numerateur (n), diviseur (1) {}
  Fraction() {}
  void reduire();
};

int main ()
{
  Fraction f1 (3, 4); // Fraction(int, int)
  Fraction f2 = 2;    // Fraction(int)
  Fraction f3;        // Fraction()
  Fraction f4 = f1;   // Fraction(const Fraction&)
}

Fraction : paramètre par défaut (7/9)

struct Fraction {
  int numerateur, diviseur;
  Fraction (int, int = 1);
  Fraction () {}
  void reduire ();
};

// Implémentation
Fraction::Fraction(int n, int d)
  : numerateur (n), diviseur (d) { reduire(); }

int main ()
{
  Fraction f1 (3, 4); // Fraction(int, int);
  Fraction f2 (2);    // Fraction(int, int = 1);
  Fraction f3;        // Fraction();
  Fraction f4 = f1;   // Fraction(const Fraction&)
}

Fraction : Surcharge d'opérateur (8/9)

#include "fraction.h"

Fraction operator + (Fraction lhs, Fraction rhs)
{
  return Fraction (lhs.numerateur * rhs.diviseur +
                   rhs.numerateur * lhs.diviseur,
                   lhs.diviseur * rhs.diviseur);
}

int main ()
{
  Fraction f1 (3, 4);
  f1 = operator + (f1, Fraction (1, 4));
  f1 = f1 + Fraction (1, 4);
  f1 = operator + (f1, 3);
  f1 = f1 + 3;
}

Fraction : Surcharge d'autres opérateurs

// Fraction(1,2) * Fraction(3,4)
Fraction operator * (Fraction lhs, Fraction rhs) {
  return Fraction f(lhs.numerateur * rhs.numerateur,
                    lhs.diviseur   * rhs.diviseur);
}

// std::cout << Fraction(1,2)    << '\n';
#include <ostream>
std::ostream & operator << (std::ostream & s, Fraction f) {
  s << f.numerateur;
  if (f.diviseur != 1)
    s << '/' << f.diviseur;
  return s;
}

// quelques autres opérateurs utiles
bool operator==(Fraction, Fraction);  // opérateur binaire
Fraction& Fraction::operator+=(Fraction); // op. binaire membre
Fraction operator-(Fraction);        // opérateur unaire
Fraction operator++(Fraction&);      // opérateur préfixé
Fraction operator++(Fraction&, int); // opérateur postfixé
Fraction::operator double() const;   // opérateur de conversion

Fraction : Réponse finale (9/9)

fraction.h

#ifndef FRACTION_H
#define FRACTION_H

#include <iosfwd>

struct Fraction {
  int numerateur, diviseur;
  Fraction ();
  Fraction (int, int = 1);
  void reduire ();
};

Fraction operator + (Fraction, Fraction);
Fraction operator * (Fraction, Fraction);
std::ostream & operator << (std::ostream &, Fraction);

#endif

fraction.cc

#include "fraction.h"
#include <cstdlib>
#include <ostream>

Fraction::Fraction() {}

Fraction::Fraction(int n, int d)
: numerateur (n), diviseur (d)
{
  reduire();
}

Fraction operator * (Fraction lhs, Fraction rhs)
{
  return
    Fraction f(lhs.numerateur * rhs.numerateur,
               lhs.diviseur   * rhs.diviseur);
}

Fraction operator + (Fraction lhs, Fraction rhs)
{
  return
    Fraction f(lhs.numerateur * rhs.diviseur +
               rhs.numerateur * lhs.diviseur,
               lhs.diviseur * rhs.diviseur);
}

std::ostream & operator << (std::ostream & s, Fraction f)
{
  s << f.numerateur;
  if (f.diviseur != 1)
    s << '/' << f.diviseur;
  return s;
}

void Fraction::reduire ()
{
  if (diviseur < 0) {
    numerateur = -numerateur;
    diviseur = -diviseur;
  }
  // PGCD numerateur et diviseur
  int a = std::abs(numerateur), b = diviseur, c;
  while (b != 0) {
    c = a % b;
    a = b;
    b = c;
  }
  numerateur /= a;
  diviseur /= a;
}

Fraction « défensive »

class Fraction {  // class au lieu de struct
  int numerateur_, diviseur_; // membres privés
public:
  class DiviseurNul {};
  Fraction (int n = 0, int d = 1)
    : numerateur_(n), diviseur_(d)
    { if (diviseur_ == 0) throw DiviseurNul(); reduire(); }
  int numerateur () const { return numerateur_; }
  int diviseur () const { return diviseur_; }
  void reduire ();
};

void f() {
  Fraction f (0, 0); // exception Fraction::DiviseurNul
  f.numerateur;      // erreur, numerateur est une méthode
  f.numerateur_;     // erreur, numerateur_ est privé
  f.numerateur();    // OK, méthode publique
}

Const-correctness

class Fraction {
//...
  int numerateur ();  // peut modifier l'objet
  int numerateur () const;  // ne modifie pas l'objet
};
void func1(Fraction &);  // peut modifier le paramètre
void func2(const Fraction &); // ne modifie pas le paramètre

void f () {
  const Fraction quart (1, 4);
  quart.numerateur ();  // int numerateur () const
}

Type gérant automatiquement une ressource

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void probleme (const char *filename)
{
  const int fd = open(filename, O_RDONLY);
  //...
  if (write (fd, buf, count) == -1)
     return;  // sortie sans close(f)
  essai();  // fonction pouvant lancer une exception
  //...
  close(f);
}

Resource acquisition is initialization

class FileHandle {
  const int handle;
public:
  FileHandle(const char* filename, int flags)
    : handle (open(filename, flags)) {}
  ~FileHandle() { close(handle); }
  operator int() const { return handle; }
};

void solution (const char *filename)
{
  const FileHandle fd (filename, O_RDONLY);
  //...
  if (write (fd, buf, count) == -1)
    return;
  //...
  // close automatique par FileHandle::~FileHandle
}

Problèmes de copie d'objet

void r (const FileHandle & handle) {
  // handle est une référence à l'objet de l'appelant
  /* ... */
}

void v (FileHandle handle) {
  // handle est un nouvel objet construit par copie du paramètre
  /* ... */
  // destruction de handle, appel automatique de close
}

void g () {
  FileHandle h (".file", O_RDONLY), h2 (".file2", O_RDONLY);
  h2 = h;  // assignation
  r(h);  // r reçoit une référence à h
  v(h);  // v reçoit une copie de h
  // destruction de h et h2, ".file" fermé 3 fois, ".file2" 0
}

Objet incopiable

class FileHandle {
  const int handle;
  FileHandle(const FileHandle&);  // Pas de copie
  FileHandle& operator=(const FileHandle&);  // Pas d'assignation
public:
  FileHandle(const char* filename, int flags)
    : handle (open(filename, flags)) {}
  ~FileHandle() { close(handle); }
  operator int() const { return handle; }
};

void f1 (FileHandle);
void f2 (const FileHandle &);

int main () {
  FileHandle h (".file", 0), h2 (".file2", 0);
  h2 = h;  // impossible d'assigner
  f1 (h);  // impossible de construire par copie
  f2 (h);  // OK
}

Copie avec transfert de possession

class FileHandle {
  bool owner;
  int handle;
public:
  FileHandle (FileHandle& from)
    : owner(from.owner), handle(from.handle)
    { from.owner = false; }  // possession transférée

  FileHandle& operator= (FileHandle& from) {
    if (&from != this) {  // cas handle=handle;
      if (owner) close(handle);  // possession abandonnée
      handle = from.handle;
      owner = from.owner;
      from.owner = false;  // nouvelle possession transférée
    }
    return *this;
  }

  ~FileHandle () { if (owner) close(handle); }
};

Exemples de comportements en cas de copie

Programmation orientée objet

Ce cours ne couvre que le support que C++ donne à la POO.

  1. On fait une conception orientée objet.
  2. On implémente dans un langage de son choix.
  3. Certains langages supportent mieux que d'autres les concepts de la POO :
  4. C++ supporte directement tout cela.

Exemple : wc (word count)

~ > wc </usr/share/common-licenses/GPL
  340  2968 17992

Objets

Schéma hiérarchique des compteurs
schéma hiérarchique des compteurs

wc : Utilisation des classes

#include "compteur.h"
#include <iostream>

int main(int argc, char * argv[])
{
  char c;
  Compteur*const compteur = MakeCompteur(argv[1]); // abstraction

  while (std::cin.get(c))
    compteur->Compter(c);  // polymorphisme
  std::cout << *compteur << '\n';
  delete compteur;
}

wc : MakeCompteur (fabrique de compteur)

#include "compteur.h"

Compteur * MakeCompteur(const char* arg)
{
  if (arg && arg[0] == '-') {
    if (arg[1] == 'w') return new CompteurDeMots;
    if (arg[1] == 'l') return new CompteurDeLignes;
    if (arg[1] == 'm') return new CompteurDeLettres;
  }
  return new CompteurDeTout;
}

wc : Héritage publique (IS-A)

class Compteur {
  public: virtual void Compter(char);
};
class CompteurDeLettres : public Compteur {
  public: void Compter(char);
};
class CompteurDeMots    : public Compteur {
  public: void Compter(char);
};
class CompteurDeLignes  : public Compteur {
  public: void Compter(char);
};
class CompteurDeTout    : public Compteur {
  public: void Compter(char);
};
std::ostream& operator<<(std::ostream&, const Compteur&);

wc : Méthodes virtuelles (pures), classe abstraite

#include <iosfwd>

class Compteur {
public:
  virtual void Compter(char) = 0;  // méthode virtuelle pure
  virtual ~Compteur() {}  // méthode (destructeur) virtuelle
  // fonction amie
  friend std::ostream& operator<<(std::ostream&, const Compteur&);
private:
  virtual std::ostream& Output(std::ostream&) const = 0;
};

std::ostream& operator<<(std::ostream&, const Compteur&);

wc : classe dérivée

#include <ostream>
#include <iomanip>

class Compteur {
public:
  virtual void Compter(char) = 0;
  virtual ~Compteur() {}
  friend std::ostream & operator<<(std::ostream &, const Compteur &);
private:
  virtual std::ostream & Output(std::ostream &) const = 0;
};

std::ostream& operator<<(std::ostream& strm, const Compteur& compteur)
{ return compteur.Output(strm); }

class CompteurDeLettres : public Compteur {
public:
  void Compter(char c) { ++compte; }
private:
  int compte;
  std::ostream & Output(std::ostream & strm) const
  { return strm << std::setw(4) << compte; }
};

wc : composition (HAS-A)

class CompteurDeTout : public Compteur {
public:
  void Compter(char);
private:
  std::ostream & Output(std::ostream &) const;
  CompteurDeLignes lignes;   // HAS-A
  CompteurDeMots mots;       // HAS-A
  CompteurDeLettres lettres; // HAS-A
};

void CompteurDeTout::Compter(char c)
{
  lettres.Compter(c);
  mots.Compter(c);
  lignes.Compter(c);
}

std::ostream & CompteurDeTout::Output(std::ostream & strm) const
{
  return strm << lignes << ' ' << mots << ' ' << lettres;
}

wc : rassembler les propriétés communes

héritage

wc : membre protégé

class CompteurSimple : public Compteur {
  public: CompteurSimple();
  protected: int compte;
  private: std::ostream & Output(std::ostream &) const;
};

class CompteurDeLettres : public CompteurSimple {
  public: void Compter(char);
};

class CompteurDeLignes : public CompteurSimple {
  public: void Compter(char);
};

class CompteurDeMots : public CompteurSimple {
public:
  CompteurDeMots();
  void Compter(char);
private:
  bool last_in_word;
};

wc : compteur.h

#ifndef COMPTEUR_H
#define COMPTEUR_H

#include <ostream>

class Compteur {
public:
  virtual void Compter(char) = 0;
  virtual ~Compteur() {}
  friend std::ostream & operator<<(std::ostream &, const Compteur &);
private:
  virtual std::ostream & Output(std::ostream &) const = 0;
};

std::ostream & operator<<(std::ostream &, const Compteur &);

class CompteurSimple : public Compteur {
public:
  CompteurSimple();
protected:
  int compte;
private:
  std::ostream & Output(std::ostream &) const;
};

class CompteurDeLettres : public CompteurSimple {
public:
  void Compter(char);
};

class CompteurDeLignes : public CompteurSimple {
public:
  void Compter(char);
};

class CompteurDeMots : public CompteurSimple {
public:
  CompteurDeMots();
  void Compter(char);
private:
  bool last_in_word;
};

class CompteurDeTout : public Compteur {
public:
  void Compter(char);
private:
  CompteurDeLignes lignes;
  CompteurDeMots mots;
  CompteurDeLettres lettres;
  std::ostream & Output(std::ostream &) const;
};

#endif

Composition et encapsulation, une base de la POO

class FileOpenDialog : public Dialog {
  TextInput filter_text;
  TextList directories_list;
  TextList files_list;
  Button open;
  Button filter;
  Button cancel;
  Button help;
public:
  FileOpenDialog(const string& dir);
};

En général, une classe applicative suffit à remplir un fichier d'en-tête et un fichier source pour l'implémentation.

dialogue d'ouverture de fichier

Héritage, une base de la POO

L'abstraction et le polymorphisme sont très adaptés à la représentation des règles business avec toutes leurs variantes.

class Compte { /*...*/ };
class ComptePersonnel : public Compte { /*...*/ };
class ComptePersonnelEUR : public ComptePersonnel { /*...*/ };
class ComptePersonnel60Plus : public ComptePersonnel { /*...*/ };
class ComptePersonnelGeneration : public ComptePersonnel { /*...*/ };
class ComptePersonnelCampus : public ComptePersonnel { /*...*/ };
class CompteCourant : public Compte { /*...*/ };
class CompteEpargne : public Compte { /*...*/ };
class CompteEpargneBonus : public CompteEpargne { /*...*/ };
class CompteEpargneJeunesse : public CompteEpargne { /*...*/ };
class CompteFondPlacement : public Compte { /*...*/ };
class CompteATerme : public Compte { /*...*/ };

wc : Par héritage multiple virtuel, schéma

Schéma d'héritage virtuel

wc : Par héritage multiple virtuel

class Compteur { /* ... */ };
class CompteurSimple   : virtual public Compteur { /* ... */ };
class CompteurDeLettres: public CompteurSimple { /* ... */ };
class CompteurDeMots   : public CompteurSimple { /* ... */ };
class CompteurDeLignes : public CompteurSimple { /* ... */ };

class CompteurDeTout
: public CompteurDeLignes
, public CompteurDeMots
, public CompteurDeLettres {
public:
  void Compter(char);
protected:
  std::ostream & Output(std::ostream &) const;
};

void CompteurDeTout::Compter(char c)
{
  CompteurDeLettres::Compter(c);
  CompteurDeMots::Compter(c);
  CompteurDeLignes::Compter(c);
}

std::ostream & CompteurDeTout::Output(std::ostream & strm) const
{
  CompteurDeLignes::Output(strm) << ' ' <<
  CompteurDeMots::Output(strm) << ' ' <<
  CompteurDeLettres::Output(strm);
  return strm;
}

Héritage privé

class Incopiable {
  Incopiable(Incopiable&);
  Incopiable& operator=(Incopiable&);
public:
  Incopiable() {}
};

class CompteurDeTout : public Compteur, Incopiable { /*...*/ };

void f () {
  CompteurDeTout compteur;
  Compteur * pc = &compteur;
  Incopiable * pi = &compteur;
}

Exceptions

class Stack {
  char* v;
  int max_size;
  int top;
public:
  class UnderFlow {};
  class OverFlow {};

  Stack(int size);
  ~Stack();
  void Push(char);
  char Pop();
};

void Stack::Push(char c) {
  if (top == max_size) throw OverFlow();
  v[top++] = c;
}

char Stack::Pop() {
  if (top == 0) throw UnderFlow();
  return v[--top];
}

Capture d'exception

void f () {
  Stack stk(3);
  try {
    stk.Push("foo");
  } catch (const Stack::Overflow & e) {
    Alerte();
  } catch (const Stack::Underflow & e) {
    Oups();
  } catch (...) {
    Arg();
  }
}

Spécifications d'exception

void f () throw (E1);

Templates

template <typename T>
const T& Max (const T& a, const T& b)
{
  return a > b ? a : b;
}

#include <iostream>
#include <string>
using namespace std;
int main ()
{
  cout << Max (5, 3) << ' '  // int max(int,int)
       << Max ('a', 'z') << ' '  // char max(char,char)
       << Max (1.21e-5, 0.0000131) << ' '  // float max(float,float)
       << Max (string("titi"), string("toto")) << ' '  // std::string max(std::string,std::string
       << Max<int>(100, 'Z') << '\n';
}
5 z 1.31e-05 toto 100

Instantiation des templates

Classe template

template<class T> class Stack {
  T* v;
  int max_size;
  int top;
public:
  class UnderFlow {};
  class OverFlow {};
  Stack(int size);
  ~Stack();
  void Push(T);
  T Pop();
};

template<class T> void Stack<T>::Push(T c) {
  if (top == max_size) throw OverFlow();
  v[top++] = c;
}

template<class T> T Stack<T>::Pop() {
  if (top == 0) throw UnderFlow();
  return v[--top];
}

Spécialisation de template

#include <cstring>

template <class T> void Copy (T * dest, const T * source)
{
  while (*dest++ = *source++);
}

template <char> void Copy (char * dest, const char * source)
{
  std::strcpy (dest, source);
}

int main()
{
  char sc[] = { 'h', 'e', 'l', 'l', 'o', '\0' };
  int si[] = { 1, 2, 3, 4, 5, 0 };
  char tc[10];
  int ti[10];
  Copy (tc, sc);
  Copy (ti, si);
}

Spécialisation partielle de template

template<class T> class Vector<T> { /*...*/ };
template<> class Vector<void*> { /*...*/ };

template <class T> class Vector<T*> : private Vector<void*> {
public:
  typedef Vector<void*> Base;
  Vector() : Base() {}
  explicit Vector(int i) : Base(i) {}
  T*& elem(int i)
    { return static_cast<T*&>(Base::elem(i)); }
  T*& operator[](int i)
    { return static_cast<T*&>(Base::operator[] (i)); }
};

Templates : métaprogrammation

// définition récursive
template <int N> struct factorielle {
  enum { value = N * factorielle<N-1>::value };
};

// condition terminale avec une spécialisation
template<> struct factorielle<0> {
  enum { value = 1 };
};

#include <iostream>
int main()
{
  std::cout << factorielle<0>::value << ' '
            << factorielle<1>::value << ' '
            << factorielle<2>::value << ' '
            << factorielle<3>::value << '\n';
}

Métaprogrammation et sélection de type

template <bool b, typename TRUE, typename FALSE>
struct IF;

template <typename TRUE, typename FALSE>
struct IF <true, TRUE, FALSE>
{
  typedef TRUE RESULT;
};
 
template <typename TRUE, typename FALSE>
struct IF <false, TRUE, FALSE>
{
  typedef FALSE RESULT;
};

#include <iostream>
int main()
{
  IF<true, int, float>::RESULT i = 1.23;
  IF<false, int, float>::RESULT f = 1.23;
  std::cout << i << ' ' << f << '\n';
}

Templates : Conclusion

Bibliothèque standard

Conteneurs

Exemple : mélangeur de lignes

#include <iostream>
#include <iomanip>
#include <vector>
#include <iterator>
#include <algorithm>
using namespace std;

int main()
{
  vector<string> v;
  string line;

  while (getline(cin, line))
    v.push_back(line);
  random_shuffle(v.begin(), v.end());
  copy(v.begin(), v.end(), ostream_iterator<string>(cout, "\n"));
}

Objets fonctionnels

// random_shuffle utilise une fonction
int alea(int max) { return std::rand() % max; }

random_shuffle(v.begin(), v.end(), alea);

// random_shuffle utilise un objet
class Alea {
  int a;
public:
  Alea (int i) : a(i) {}
  int operator() (int max) { ++a; return a %= max; }
};

Alea obj (22);
random_shuffle(v.begin(), v.end(), obj);

Exception-safety et STL

Garantie de base et forte

Que se passe-t-il lorsqu'une exception est jetée par une opération dans un conteneur de la bibliothèque standard.

Streams : Formattage de la sortie

C'est pas très joli, mais possible.

#include <iostream>
#include <iomanip>
using namespace std;

int main ()
{
  int i = 10;
  cout << i << ' ' << hex << i << ' ' << oct << i << '\n';

  double f[] = { 0, 132.456, -0.0113, 12345678 };
  cout << fixed << setprecision(2) << setfill('_');
  for (size_t i = 0; i < sizeof f / sizeof f[0]; ++i)
    cout << setw(8) << f[i] << '\n';
}
10 a 12
____0.00
__132.46
___-0.01
12345678.00

Autres streams

I/O

#include <iostream>
int main()
{
  int a, b;
  std::cout << "Entrez deux entiers :" << std::endl;
  std::cin >> a >> b;
  std::cout << "somme = " << a + b <<
               " produit = " << a * b << std::endl;
}

stringstream

#include <sstream>
std::string fullpath (const char* basedir, const char* filename)
{
  std::ostringstream strm;
  strm << basedir << '/' << filename;
  return strm.str();
}

Fichiers

#include <fstream>
int main (int argc, char *argv[])
{
  std::ifstream file (argv[1]);
  //...
}

©2006, Marc Mongenet Creative Commons License
Ce cours est disponible selon les termes de la Creative Commons Attribution 2.5 License.