C++ - Définition

Source: Wikipédia sous licence CC-BY-SA 3.0.
La liste des auteurs de cet article est disponible ici.

L'encapsulation en C++

L'encapsulation permet de faire abstraction du fonctionnement interne (c'est-à-dire, la mise en œuvre) d'une classe et ainsi de ne se préoccuper que des services rendus par celle-ci. C++ met en œuvre l'encapsulation en permettant de déclarer les membres d'une classe avec le mot réservé public, private ou protected. Ainsi, lorsqu'un membre est déclaré :

  • public, il sera accessible depuis n'importe quelle fonction.
  • private, il sera uniquement accessible d'une part, depuis les fonctions qui sont membres de la classe et, d'autre part, depuis les fonctions autorisées explicitement par la classe (par l'intermédiaire du mot réservé friend).
  • protected, il aura les mêmes restrictions que s'il était déclaré private, mais il sera en revanche accessible par les classes filles.

C++ n'impose pas l'encapsulation des membres dans leurs classes. On pourrait donc déclarer tous les membres publics, mais en perdant une partie des bénéfices apportés par la programmation orientée objet. Il est de bon usage de déclarer toutes les données privées, ou au moins protégées, et de rendre publiques les méthodes agissant sur ces données. Ceci permet de cacher les détails de la mise en œuvre de la classe.

« Hello, world »

Voici l'exemple de Hello world donné dans The C++ Programming Language, Third Edition de Bjarne Stroustrup :

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

Une importante notion de C++ sont les espaces de noms (namespaces). Dans un espace de noms sont définis des noms de fonctions et de variables. Ce mécanisme permet de résoudre les ambiguïtés lorsque plusieurs variables provenant de différents composants sont homonymes. Pour recourir à une fonction d'un espace de nom, l'opérateur de résolution de portée « :: » est utilisé.

      std::cout      

Ce code source fait appel à la variable globale cout définie dans l'espace de nom standard (std). Il est possible de spécifier un espace de nom précis à utiliser afin d'éviter d'avoir à recourir à l'opérateur de résolution de portée. Pour cela, le mot clé using est utilisé avec cette syntaxe :

      using namespace nom_du_namespace;      

Ainsi, pour utiliser la variable cout définie dans le namespace standard sans utiliser l'opérateur de résolution de portée, il est possible d'écrire :

      using namespace std;      

Cela est valable pour tous les espaces de noms. Cette instruction se place en général avant le début du code source proprement dit :

      # include       using namespace std;      int main()      {          cout << "Hello, new world!" << endl;          return 0;      }      

Déclaration de classe

Exemple de la déclaration de la classe MessageInternet comportant des attributs privés et des méthodes publiques dont le constructeur 'MessageInternet' :

       class MessageInternet       {        private:         string m_sSujet;         string m_sExpediteur;         string m_sDestinataire;        public:         MessageInternet (string sujet, string expediteur, string destinataire);         string GetSujet () const;         string GetExpediteur () const;         string GetDestinataire () const;       };      

Déclaration de templates

À quoi servent les templates ?

Les templates permettent d'écrire des fonctions et des classes en paramétrant le type de certains de leurs constituants (type des paramètres ou type de retour pour une fonction, type des éléments pour une classe collection par exemple). Les templates permettent d'écrire du code générique, c'est-à-dire qui peut servir pour une famille de fonctions ou de classes qui ne diffèrent que par la valeur de ces paramètres.

Paramètres des templates

Les paramètres peuvent être de différentes sortes :

  • Types simples: class, struct, types élémentaires comme int, float, etc.
  • Tableaux de taille constante, dont la taille, déduite par le compilateur, peut être utilisée dans l'instanciation du template.
  • Constantes scalaires, c'est-à-dire de type dérivant des entiers (int, long, bool), mais ni double ou float (Car leur représentation binaire ne fait pas partie de la norme du langage C++).
  • Templates: La définition d'un template peut être passée à un template, ce qui permet notamment de s'appuyer sur la définition abstraite, par exemple, d'un conteneur.
  • Pointeurs ou références, à condition que leur valeur soit définie à l'édition de liens.
  • Fonction membre, dont la signature et la classe doivent être aussi passées en paramètres.
  • Membre d'une classe, dont le type et la classe doivent être aussi passés en paramètres du template.

Pourquoi utiliser des templates ?

En programmation, il faut parfois écrire de nombreuses versions d'une même fonction ou classe suivant les types de données manipulées.

Par exemple, un tableau de int ou un tableau de double sont très semblables, et les fonctions de tri ou de recherche dans ces tableaux sont identiques au type près.

En résumé, l'utilisation des templates permet de « paramétrer » le type des données manipulées.

Avantages à utiliser des templates

  • écritures uniques pour les fonctions et les classes.
  • moins d'erreurs dues à la réécriture
  • Performances améliorées grâce a la spécialisation en fonction des types de données.

Exemple de templates

Dans la bibliothèque standard C++, on trouve de nombreux templates. On citera à titre d'exemple, les entrées/sorties, les chaînes de caractères ou les conteneurs. Les classes string, istream, ostream et iostream sont toutes des instanciations de type char.

Les fonctions de recherche et de tri sont aussi des templates écrits et utilisables avec de nombreux types.

      // La fonction template Max peut être appelée avec tout type copiable      // et comparable avec l'opérateur <.      template <typename T> T Max(T a, T b)      {          return a < b ? b : a;      }             # include       int main()  // fonction main      {          int i = Max(3, 5);          char c = Max('e', 'b');          std::string s = Max(std::string("hello"), std::string("world"));          float f = Max<float>(1, 2.2f);          return 0;      }      

Dans la ligne float f = Max(1, 2.2f), on doit explicitement donner le type float pour le type paramétré T car le compilateur ne déduit pas le type de T lorsqu'on passe en même temps un int (1) et un float (2.2f).

Spécialisation des templates

Un template donné peut avoir plusieurs instanciations possibles selon les types donnés comme paramètres. Si un seul paramètre est spécialisé, on parle de spécialisation partielle. Ceci permet par exemple:

  • De choisir un type de calcul selon qu'un type est un entier, un nombre flottant, une chaîne de caractères, etc. Spécialisons l'exemple précédent pour le cas des pointeurs de chaines de caractères :
      template <> const char * Max(const char * a, const char * b)      {          return (strcmp( a, b ) > 0) ? a : b;      }      
  • D'effectuer au moment de la compilation des calculs arithmétiques, si et seulement si tous les arguments sont connus à ce moment. Un exemple classique est le calcul de la fonction factorielle:
      template< size_t N >      struct CalcCompileTime      {          static size_t Fact = N * CalcCompileTime< N - 1 > ;      };             template<0>      struct CalcCompileTime<0>      {          static size_t Fact = 1 ;      };      
Page générée en 0.115 seconde(s) - site hébergé chez Contabo
Ce site fait l'objet d'une déclaration à la CNIL sous le numéro de dossier 1037632
A propos - Informations légales
Version anglaise | Version allemande | Version espagnole | Version portugaise