Chapitre 13. Services et notions de base de la bibliothèque standard

Table des matières
13.1. Encapsulation de la bibliothèque C standard
13.2. Définition des exceptions standards
13.3. Abstraction des types de données : les traits
13.4. Abstraction des pointeurs : les itérateurs
13.5. Abstraction des fonctions : les foncteurs
13.6. Gestion personnalisée de la mémoire : les allocateurs
13.7. Notion de complexité algorithmique

La bibliothèque standard C++ fournit un certain nombre de fonctionnalités de base sur lesquelles toutes les autres fonctionnalités de la bibliothèque s'appuient. Ces fonctionnalités apparaissent comme des classes d'encapsulation de la bibliothèque C et des classes d'abstraction des principales constructions du langage. Ces dernières utilisent des notions très évoluées pour permettre une encapsulation réellement générique des types de base. D'autre part, la bibliothèque standard utilise la notion de complexité algorithmique pour définir les contraintes de performance des opérations réalisables sur ses structures de données ainsi que sur ses algorithmes. Bien que complexes, toutes ces notions sont omniprésentes dans toute la bibliothèque, aussi est-il extrêmement important de les comprendre en détail. Ce chapitre a pour but de vous les présenter et de les éclaircir.

13.1. Encapsulation de la bibliothèque C standard

La bibliothèque C définit un grand nombre de fonctions C standards, que la bibliothèque standard C++ reprend à son compte et complète par toutes ses fonctionnalités avancées. Pour bénéficier de ces fonctions, il suffit simplement d'inclure les fichiers d'en-tête de la bibliothèque C, tout comme on le faisait avec les programmes C classiques.

Toutefois, les fonctions ainsi déclarées par ces en-têtes apparaissent dans l'espace de nommage global, ce qui risque de provoquer des conflits de noms avec des fonctions homonymes (rappelons que les fonctions C ne sont pas surchargeables). Par conséquent, et dans un souci d'homogénéité avec le reste des fonctionnalités de la bibliothèque C++, un jeu d'en-têtes complémentaires a été défini pour les fonctions de la bibliothèque C. Ces en-têtes définissent tous leurs symboles dans l'espace de nommage std::, qui est réservé pour la bibliothèque standard C++.

Ces en-têtes se distinguent des fichiers d'en-tête de la bibliothèque C par le fait qu'ils ne portent pas d'extension .h et par le fait que leur nom est préfixé par la lettre 'c'. Les en-têtes utilisables ainsi sont donc les suivants :

cassert
cctype
cerrno
cfloat
ciso646
climits
clocale
cmath
csetjmp
csignal
cstdarg
cstddef
cstdio
cstdlib
cstring
ctime
cwchar
cwctype

Par exemple, on peut réécrire notre tout premier programme que l'on a fait à la Section 1.9 de la manière suivante :

#include <cstdio>

long double x, y;

int main(void)
{
    std::printf("Calcul de moyenne\n");
    std::printf("Entrez le premier nombre : ");
    std::scanf("%Lf", &x);
    std::printf("\nEntrez le deuxième nombre : ");
    std::scanf("%Lf", &y);
    std::printf("\nLa valeur moyenne de %Lf et de %Lf est %Lf.\n",
        x, y, (x+y)/2);
    return 0;
}

Note : L'utilisation systématique du préfixe std:: peut être énervante sur les grands programmes. On aura donc intérêt soit à utiliser les fichiers d'en-tête classiques de la bibliothèque C, soit à inclure une directive using namespace std; pour intégrer les fonctionnalités de la bibliothèque standard dans l'espace de nommage global.

Remarquez que la norme ne suppose pas que ces en-têtes soient des fichiers physiques. Les déclarations qu'ils sont supposés faire peuvent donc être réalisées à la volée par les outils de développement, et vous ne les trouverez pas forcément sur votre disque dur.

Certaines fonctionnalités fournies par la bibliothèque C ont été encapsulées dans des fonctionnalités équivalentes de la bibliothèque standard C++. C'est notamment le cas pour la gestion des locales et la gestion de certains types de données complexes. C'est également le cas pour la détermination des limites de représentation que les types de base peuvent avoir. Classiquement, ces limites sont définies par des macros dans les en-têtes de la bibliothèque C, mais elles sont également accessibles au travers de la classe template numeric_limits, définie dans l'en-tête limits :

// Types d'arrondis pour les flottants :
enum float_round_style
{
    round_indeterminate       = -1,
    round_toward_zero         =  0,
    round_to_nearest          =  1,
    round_toward_infinity     = 2,
    round_toward_neg_infinity = 3
};

template <class T>
class numeric_limits
{
public:
    static const bool is_specialized = false;
    static T min() throw();
    static T max() throw();
    static const int digits   = 0;
    static const int digits10 = 0;
    static const bool is_signed  = false;
    static const bool is_integer = false;
    static const bool is_exact   = false;
    static const int radix = 0;
    static T epsilon() throw();
    static T round_error() throw();
    static const int min_exponent   = 0;
    static const int min_exponent10 = 0;
    static const int max_exponent   = 0;
    static const int max_exponent10 = 0;
    static const bool has_infinity  = false;
    static const bool has_quiet_NaN = false;
    static const bool has_signaling_NaN = false;
    static const bool has_denorm        = false;
    static const bool has_denorm_loss   = false;
    static T infinity() throw();
    static T quiet_NaN() throw();
    static T signaling_NaN() throw();
    static T denorm_min() throw();
    static const bool is_iec559  = false;
    static const bool is_bounded = false;
    static const bool is_modulo  = false;
    static const bool traps      = false;
    static const bool tinyness_before = false;
    static const float_round_style
        round_style = round_toward_zero;
};
Cette classe template ne sert à rien en soi. En fait, elle est spécialisée pour tous les types de base du langage, et ce sont ces spécialisations qui sont réellement utilisées. Elles permettent d'obtenir toutes les informations pour chaque type grâce à leurs données membres et à leurs méthodes statiques.

Exemple 13-1. Détermination des limites d'un type

#include <iostream>
#include <limits>

using namespace std;

int main(void)
{
    cout << numeric_limits<int>::min() << endl;
    cout << numeric_limits<int>::max() << endl;
    cout << numeric_limits<int>::digits << endl;
    cout << numeric_limits<int>::digits10 << endl;
    return 0;
}

Ce programme d'exemple détermine le plus petit et le plus grand nombre représentable avec le type entier int, ainsi que le nombre de bits utilisés pour coder les chiffres et le nombre maximal de chiffres que les nombres en base 10 peuvent avoir en étant sûr de pouvoir être stockés tels quels.