1/9

1. Introduction

Ce tutoriel propose une brève introduction au langage C++ . L’objectif est de vous permettre d’être utilisateur du langage dans un contexte de programmation graphique (en lien avec OpenGL). Pour cela, nous nous intéresserons a une sous partie des fonctionnalités du C++ qui nous sont spécialement utile pour le reste des séances pratiques orientées graphiques.

Ce tutoriel présuppose que vous connaissez d’ores et déjà un langage de programmation orienté objet (ex. Java) et à l’aise en algorithmique (ex. en utilisant Python).

Notez que le langage C++ est dit multi-paradigm. C’est-à-dire qu’il peut être utilisé dans une vaste gamme d’application, et suivant plusieurs approches. Le C++ est un langage très riche, ce qui le rend complexe, et il n’existe ainsi pas une unique manière de coder en C++ (utilisation de pointeurs à la C, pur objet, fonctionnel, etc); chaque approche pouvant se prêter plus ou moins à un type d’application ou d’habitude.

Dans le cas présent, nous utiliserons principalement une programmation procédurale, où les objets seront généralement de petites structures allouées statiquement afin de pouvoir être créées et manipulées de manière légère (en mémoire) et rapide (en temps de calcul). Nous utiliserons les surcharges d’opérateurs pour modéliser et manipuler aisément des objets mathématiques (vecteurs, matrices). Nous utiliserons également largement les conteneurs de la librairie standard permettant de gérer des objets dynamiques sans avoir à manipuler explicitement les allocations mémoires. Enfin, nous nous baserons sur la norme C++ 2011 (dit C++ moderne) afin de simplifier l’écriture et l’initialisation de certains objets de la librairie standard. A l’opposé, nous n’aborderons dans ce cours pas (ou que très marginalement) la gestion manuelle de la mémoire, l’utilisation de pointeurs, le polymorphisme, les templates.

Pour approfondir vos connaissances en C++ , il vous est possible de vous référer au cours INF442 qui aborde davantage de détails sur le langage, ainsi qu’au livre "The C++ Programming Language" de Bjarne Stroustrup (créateur du langage C++ ).

De manière générale, n’hésitez pas à rechercher des informations sur la référence en ligne du langage ainsi que de sa bibliothèque standard : CPP Reference.

Nous supposerons dans l’ensemble des TD que vous être dans un environnement Linux similaire aux distributions des PC fixe de l’école.

Tout premier programme en C++

Créez un fichier nommé main.cpp contenant le code suivant

#include <iostream>
int main()
{
    std::cout << "Hello world" << std::endl;
    return 0;
}

Rem. Vous pouvez créer et éditer un fichier texte avec l’éditeur de texte de votre choix. Vous avez notamment à disposition sur les PC de l’établissement Visual Code Studio qui permet d’éditer efficacement du code pour de nombreux langages.

Pour exécuter le programme correspondant à ce code, il est nécessaire de

  1. Compilez le code (c-a-d convertir le code en un fichier binaire exécutable par le système).

  2. Lancez l’exécutable correspondant.

Pour cela, ouvrez une ligne de commande dans le répertoire où se situe le fichier main.cpp.
Tapez ensuite la commande suivante pour compiler le programme

$ g++ main.cpp

Rem. le sigle $ n’est pas à taper, il indique une ligne de commande

Si tout se déroule correctement un fichier exécutable, nommé a.out par défaut, est généré. Tapez finalement la commande suivante pour lancer l’exécutable (Hello world devrait s’afficher en ligne de commande)

$ ./a.out

Analyse du code

  • #include <iostream>

    • #include est permet d’inclure le code d’un autre fichier. Il s’agit du mécanisme d’inclusion de bibliothèque externe en C++

    • #include <FileName> permet l’inclusion du fichier nommé FileName. Les chevrons indiquent que ce fichier doit être préférentiellement cherché dans les fichiers système (ce qui est le cas pour iostream).

    • iostream correspond à la bibliothèque standard C++ d’entrée sortie (iostream = Input Output Stream). Elle permet en particulier l’affichage sur la ligne de commande.

  • int main()

    • Correspond au point d’entrée de l’exécutable, c’est-à-dire la fonction qui va être appelée automatiquement lorsque vous lancez l’exécutable.

    • Tout programme C++ doit nécessairement contenir une (et une seule) fonction appelée main.

  • std::cout << "Hello World" << std::endl;

    • std :: cout << syntaxe standard pour afficher du texte sur la ligne de commande (ici "Hello World")

      • cout signifie Common Output. (réfère par défaut à la ligne de commande sauf si l’on a redirigé la sortie standard)

      • std::cout est le nom complet de l’objet cout qui est présent dans l’espace de nommage (namespace) std (standard library).

      • :: est appelé l’opérateur de résolution de portée (scope resolution operator). Ici il permet de trouver l’objet cout dans l’espace de nom std

      • << est un opérateur C++ (c-a-d un symbole utilisé en tant que fonction). Ici l’opérateur << est utilisé pour envoyer la chaine de caractère (string) à l’objet std::cout afin de l’afficher en ligne de commande.

    • <<std::endl Ajout un saut de ligne à la fin de l’affichage. endl réfère à end of line.

  • return 0; Valeur de retour de l’exécutable (renvoi au processus appelant - par exemple la ligne de commande). Par convention une valeur de retour de 0 indique que le programme s’est terminé sans erreurs.

Remarque générale: Chaque instruction de code C++ se termine pas un ;

Compilation

Notion de langage compilé

Le langage C++ correspond à un langage dit compilé. C’est-à-dire que le code C++ est converti en code assembleur afin d’être directement exécuté par le processeur central (CPU).

Rem. L’assembleur correspond aux instructions que peut exécuter un processeur. L’assembleur correspond à un langage de très bas niveau permettant de charger des données depuis leur adresse en mémoire dans des registres du processeur, appliquer des opérations sur les données du registre, puis les exporter à nouveau sur une mémoire de la machine. L’assembleur applique des opérations directement sur des ensembles de valeurs en binaire, et n’a pas de notions de types de données. Cette notion étant apportée par les langages de plus haut niveau.

  • L’avantage des langages compilés est leur très grande efficacité. En effet, les lignes de code sont transcrites en instructions directement exécutées par le processeur. Il n’y a pas d’étapes intermédiaires, et cela permet d’utiliser les ressources du processeur uniquement pour les instructions du code.

  • A l’opposé des langages compilés, on peut noter les langages interprétés (tel que Python, R, Scilab, etc). Les commandes des langages interprétés sont exécutées de manière indirecte par un logiciel tiers appelé interpréteur. L’interpréteur lit et exécute chaque ligne du code, et vient lui-même réaliser son exécution. L’interpréteur correspond généralement lui-même à un logiciel compilé (souvent écrit en C ou C++ ). Les langages interprétés correspondent généralement à des langages de haut niveau dynamique (type adaptable automatiquement, allocation mémoire automatiquement gérée, etc), mais qui sont moins efficaces lors de leur exécution et plus couteux en mémoire.

  • On trouve également des langages intermédiaires tournant sur une machine virtuelle. Les langages tels que Java par exemple possèdent une étape de compilation, mais l’assembleur résultant, appelé byte code, ne correspond pas à un assembleur exécutable par le CPU. Le byte code est exécuté par un logiciel (ex. Java Virtual Machine) qui émule un ordinateur (mémoire, registre). L’utilisation de ce byte code est à mi-chemin entre les langages interprétés (portable/indépendant de la machine, dynamique), et compilés (exécutions d’instructions simples de manière optimisée). Ces langages restent cependant de manière générale moins efficaces et plus couteux en mémoire qu’un code compilé directement en assembleur CPU pour une architecture donnée.

Compilateurs sous Unix

Sous système Linux et Unix, deux compilateurs libres sont couramment utilisés pour le C++

  • GCC, le compilateur historique du projet GNU

  • Clang, un compilateur plus récent basé sur l’infrastructure de compilation LLVM.

Nous utiliserons par défaut GCC dans les énoncés (la syntaxe de la grande majorité des options des deux compilateurs est identique).

Nous avons vu que l’appel à g++ main.cpp permet la compilation du fichier nommé main.cpp et génère un fichier exécutable a.out. Dans le cas général, nous allons passer plus d’options à l’étape de compilation, en particulier

  • -g pour permettre le debug de l’exécutable (ajout d’informations dans le fichier exécutable).

  • -Wall -Wextra permet d’activer l’ensemble des messages de warnings du compilateur. Les warnings permettent d’indiquer au développeur des parties de code syntaxiquement viables, mais qui sont probablement source de problèmes, ou de mauvaise qualité.

  • -O2 permet au compilateur d’optimiser le code assembleur généré (les compilateurs C++ peuvent simplifier et optimiser des expressions pour permettre une exécution plus rapide. ex. Dé-bouclage d’itérations, suppression des instructions inutiles, précalculs dont le résultat est connu à la compilation, inclusions de code à la place d’appel à des fonctions, réorganisation des octets des structures, etc).

  • -o pgm indique que le fichier de sortie se nomme pgm plutôt que a.out par défaut. (Vous pouvez changer pgm par le nom de votre choix).

  • -std=c++11 indique que le code C++ compilé respecte la norme dite de C++ 2011. Rem. Cette option est à ajouter pour le PC de l’école dont le compilateur gcc par défaut est ancien. Avec un gcc récent, cette option n’est plus nécessaire (la version compilée par défaut est en C++ 2014)

Au final, la compilation suivie de l’exécution du programme main.cpp peut se réaliser avec la ligne de commande suivante

$ g++ main.cpp -o pgm -g -O2 -Wall -Wextra -std=c++11
$ ./pgm

Rem. Il est inutile de recompiler le programme à chaque exécution du programme. La compilation ne doit être relancée que si le code source du programme subit des modifications.

Variante du premier programme

Namespace std

L’ensemble des objets et fonctions de la bibliothèque standard sont placés dans l’espace de nom std. L’utilisation de cet espace de nom permet de définir d’autres objets et fonction possédant un nom similaire (ex. cos, size, vector, array, etc), mais possédant une implémentation différente (ex. std::vector et maLib::vector).

Il est possible d’éviter d’avoir à spécifier explicitement l’espace de nom à chaque appel en utilisant la syntaxe using std::X dans l’en-tête du programme (où X est à remplacer par la fonction/objet souhaitée).

Le programme précédent peut ainsi s’écrire

#include <iostream>



using std::cout;
using std::endl;



int main()
{
    cout << "Hello world" << endl;
    return 0;
}

Remarques

  • L’utilisation de la directive std::X à chaque appel VS using std::X en début de fichier est une question de choix personnel (et d’absence de conflit de noms).

  • Il est possible d’inclure l’ensemble des objets et fonctions d’un espace de nom dans l’espace commun (global scope) en utilisant la directive using namespace X, où X est l’espace de nom.

  • Include l’ensemble des fonctions de la bibliothèque standard dans l’espace de nom commun (using namespace std;) est considéré comme une mauvaise pratique. Même si vous trouvez sur internet des codes utilisant cette directive, prenez l’habitude de ne pas le faire. (explication rapide: La bibliothèque standard contient un très grand nombre d’objets et fonctions. De plus les implémentations peuvent varier en fonction des systèmes. Il est ainsi possible d’avoir un code compilant sur un système et en conflit de nom sur un autre.)

C++ moderne

Le langage C++ possède une longue histoire depuis sa création initiale en 1985. Le langage fut initialement conçu comme une extension du langage C avec l’introduction de l’orienté objet. Il reste encore aujourd’hui compatible avec le langage C, mais peut être utilisé suivant de multiples approches.

  • En 1998, le langage C++ est normalisé (ISO/IEC) dans sa version C++98

  • Une version avec de nombreux ajouts majeures est apportée en 2011 dans la version C++11, appelée également C++ moderne. Différents ajouts se sont poursuivis et l’on peut noter les versions C++14, C++17. La prochaine à venir étant C++21.

La bonne utilisation de la syntaxe C++ moderne permet d’aboutir à des codes plus simples et lisibles, tout en gardant une efficacité optimale d’exécution.

Exemple d’utilisation de code compatible C++11

#include <iostream>



int main()
{
    auto list = {1.1, 5.7, 6.5, 7.2, -12.1, 6.3};



    for(auto element : list)
        std::cout << element << std::endl;



    return 0;
}
  • auto Type automatiquement déterminée au moment de la compilation (dans le cas présent, il s’agit d’un initializer_list de double)

  • for(auto element : list) { …​ } permet le parcours de l’ensemble des éléments d’un conteneur.