L’objet de la classe std::array permet de représenter en mémoire un tableau statique dont le nombre d’éléments est connue à la compilation et est fixé à l’avance.
Prérequis :
– Savoir lire du C++
Explications :
On peut l’initialiser d’emblée :
C++
1
std::array<int,5>a={1,2,3,4,5};
Voilà une déclaration supplémentaire :
C++
1
2
3
#include <array>
std::array<int,3>UnArray;
Les données de ce type de tableau sont stockées de manière contigu en mémoire.
Résumé :
A l’opposé des tableaux classiques, les tableaux déclarés avec la classe std::array stocke aussi le nombre d’éléments du tableau en mémoire.
La documentation d’un programme est une étape fondamentale dans sa compréhension.
Le programme Doxygen permet la générer automatiquement sous couvert d’alimenter le programme en commentaires de code !
Prérequis :
Explications :
Voici les tags à utiliser dans vos programmes en tant que commentaires :
\struct pour documenter une structure C. \union pour documenter une union C. \enum pour documenter un type énuméré. \fn pour documenter une fonction. \var pour documenter une variable / un typedef / un énuméré. \def pour documenter un #define. \typedef pour documenter la définition d’un type. \file pour documenter un fichier. \namespace pour documenter un namespace. \package pour documenter un package Java. \interface pour documenter une interface IDL. \brief pour donner une description courte. \class pour documenter une classe. \param pour documenter un paramètre de fonction/méthode. \warning pour attirer l’attention. \author pour donner le nom de l’auteur. \return pour documenter les valeurs de retour d’une méthode/fonction. \see pour renvoyer le lecteur vers quelque chose (une fonction, une classe, un fichier…). \throws pour documenter les exceptions possiblement levées. \version pour donner le numéro de version. \since pour faire une note de version (ex : disponible depuis…). \exception pour documenter une exception. \deprecated pour spécifier qu’une fonction/méthode/variable… n’est plus utilisée. \li pour faire une puce. \todo pour indiquer une opération restant « à faire ». \fixme pour indiquer un code défectueux, « à réparer ».
Résumé :
Nous avons présenté le programme Doxygen qui vous permettra de documenter à la volée tout votre code !
Parfois dans un programme on veut retranscrire rapidement un morceau de chaîne de caractères (en entrée) vers un autre type (en arrivée) que l’on souhaite.
Prérequis :
– Savoir lire du C++
– Être curieux !
Explications :
Pour se faire, on utilisera la fonction définie dans l’en-tête C++ nommée swscanf. Cette fonction lit les données d’une chaîne de caractère étendue et l’analyse selon un format spécifié. Le résultat est stocké dans un endroit spécifié dans les paramètres additionnels.
– ws: une chaîne de caractère étendue / large (sur 32 bits) que la fonction se sert pour recevoir les données à traiter.
– format: contient une chaîne de caractères spécifiant le schéma / patron de la chaîne à analyser ; le format spécifié suit le même schéma que la fonction scanf.
– …: il s’agit des paramètres additionnels qui vont servir à stocker les valeurs sortantes.
Valeur de retour :
– La valeur de retour 0 indique qu’aucun champ n’a été assigné
– Retourne le nombre de champs qui sont correctement convertis et assignés
Exemple :
C++
1
2
3
4
5
6
wchar_tsentence[]=L"Clément a 28 ans";
wchar_tstr[20];
inti;
swscanf(sentence,L"%ls %*s %d",str,&i);
wprintf(L"%ls -> %d\n",str,i);
Affiche : Clément -> 28.
Résumé :
Nous avons présenté une méthode pour convertir une chaîne formatée en entrée.
Les exceptions, en C++, sont un moyen de gérer les erreurs dans les programmes.
Prérequis :
– Savoir lire du C++
Explications :
Pour expliquer rapidement, une exception est une situation donnée dans laquelle un programme, qui dans une circonstance particulière, lance une erreur.
Chaque exception lancée dans le programme doit être traitée, sinon le programme se termine et génère une erreur fatale.
L’apparition d’une exception interrompt l’exécution normale du programme et provoque sa reprise dans le gestionnaire d’exception le plus proche.
Elles sont déclenchées grâce à l’utilisation du mot-clé throw.
Il est conseillé de dériver chaque exception par celle std::exception (en tête ).
Cette classe dispose d’une fonction membre what() qui renvoie une description de l’exception.
On rattrape les exception par référence, on les envoie par valeur.
Exemple d’exception :
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
voidUneFonction()
{
[...]
throwstd::exception("Une erreur s'est lancée !");
Les threads sont utilisés dans ce qu’on nomme « la programmationconcurrente», qui consiste en l’exécution des processus légers. Cela représente une séquence d’exécution du code d’un programme au sein d’un processus. Quelque part, les threads sont des processus légers qui sont exécutés « à l’intérieur » d’un processus.
Les threads d’un même processus partagent la même mémoire et leurs variables. La pile de chaque thread est cependant unique pour chacun d’entre-eux.
Un processus habituel possède un seul thread et ce dernier correspond tout simplement au processus lui-même.
Soit c’est le noyau du système d’exploitation qui s’occupe des threads, soit ils sont gérés dans l’espace utilisateur à travers un gestionnaire spécial !
Prérequis :
– Savoir lire du C++
Explications :
Exemple d’utilisation d’un std::thread :
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <thread>
voidf()
{
std::cout<<"Un thread C++11"<<std::endl;
}
intmain()
{
std::threadt(f);
t.join();
return0;
}
La méthode .join() permet d’assurer que le thread se termine avant de quitter le programme ou la portée du code. Cet appel est bloquant!
Un thread est dans l’état « joignable » s’il est est créé avec une tâche à réaliser.
Le type std::thread (C++11) a une méthode get_id() afin d’obtenir l’id du thread en question.
Il peut exister un problème de synchronisation entre deux threads ! On doit protéger l’accès à certaines ressources si deux threads distincts sont en train d’y accéder en écriture.
Un thread peut être aussi détaché :
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <thread>
voidf()
{
std::cout<<"Un thread C++11"<<std::endl;
}
intmain()
{
std::threadt(f);
t.join();
t.detach();
return0;
}
Qu’est-ce qu’un sémaphore ?
Il permet de synchroniser l’accès à une ressource partagée. Cette ressource peut aussi bien être un bout de code !
Les threads accèdent aux ressources par le biais de ce sémaphore.
Pendant que le thread exécute cette ressource partagée aucun autre thread ne peut y accéder. Dès qu’il a terminé son travail, il libère le sémaphore (qui est nommé aussi « moniteur »).
Qu’est-ce qu’un mutex ?
Le mot « mutex » provient de l’abréviation « Mutual Exclusion ». Il assure que les données partagées sont « mutuellement exclusives ». Il s’agit juste d’une variable qui peut être soit dans l’état « verrouillé » ou soit dans l’état « déverrouillé » !
Pour verrouiller un mutexon utilise la méthode lock().
Dès qu’un thread a fini de travailler avec une donnée / ressource partagée il doit libérer le mutex! Il faut utiliser la méthode unlock().
On peut déclarer le mutexen même temps que la variable à protéger !
C++
1
2
3
4
5
structData
{
intvar;
std::mutex mutex;
};
Qu’est-ce qu’une condition ?
Une conditionest nécessaire d’être utilisée si l’on veut qu’un thread patiente à l’événement d’un autre thread.
Lorsqu’un thread est en attente d’une condition, il reste bloqué jusqu’à ce que la condition soit réaliser par un autre thread.
Résumé :
Nous avons présenter l’utilisation des thread (processus léger) afin de répartir l’exécution des bouts de code d’un jeu-vidéo.
En effet, les programmes utilisant des threads sur des architectures de type multi-processeurs sont plus rapides que les programmes plus classiques !
std::cout<<"Erreur de l'ouverture du fichier !"<<std::endl;
}
Remarque : on utilise la classe ifstream (Input File) pour la lecture de fichier et ofstream (Output File stream) pour l’écriture de fichier.
Il faut noter que l’on peut lire sur le flux avec l’opérateur chevrons (>>) mot par mot. Et qu’on peut lire ligne par ligne avec la fonction std::getline().
Résumé :
Nous avons vu comment lire ligne par ligne un fichier par la fonction std::getline().
Parfois on a besoin de combiner et de regrouper des paramètres d’une enum ensemble.
Prérequis :
– Comprendre l’essentiel de la logique booléenne
– Savoir ce qu’est une énumération en C++
Explications :
Par exemple, pour gérer les entrées clavier des touches SHIFT, CTRL, ALT, nous devons parfois les combiner afin d’effectuer une action spéciale dans une application.
On pourrait déclarer ces possibilités par l’énumération de la manière suivante :
C++
1
2
3
4
5
6
enumKeyModifier
{
SHIFT,
CTRL,
ALT
};
Mais on aimerait les combiner avec l’opérateur booléen : | (le ou logique) de la façon suivante :
C++
1
2
// Cette fonction reçoit deux champs d'une énumération combinés
KeyInputReceiver(SHIFT|CTRL);
Cette combinaison représente le fait que l’on appuie en même temps sur la touche SHIFT et sur la touche CTRL.
Alors il est nécessaire, pour combiner ces derniers champs, de spécifier des valeurs d’une certaine façon à cette énumération :
C++
1
2
3
4
5
6
enumKeyModifier
{
SHIFT=0x01,
CTRL=0x02,
ALT=0x03
};
Ceci afin de nous permettre de faire des opérations binaires (bitwise) sur ces derniers champs.
Pour simplifier cette dernière déclaration, on utilisera cette macro suivante qui aidera à assigner, dans votre déclaration enum, les différents champs binaires (bit flag)
[code language=cpp]
#define BIT(x) 1 << x
[/cpp]
Mais que fait cette dernière macro ?
Elle remplace les champs de déclarations binaires de manière simple.
C++
1
2
3
4
5
6
enumKeyModifier
{
SHIFT=BIT(1),
CTRL=BIT(2),
ALT=BIT(3)
};
Résumé :
Nous avons appris comment combiner plusieurs paramètres d’une énumération en C++.
Quand on écrit une chaine de caractères avec des caractères de l’alphabet français (é, ù, ç, à, etc…) dans la console, ils s’affichent de la mauvaise façon.
Exemple :
C++
1
2
3
4
5
6
#include <iostream>
intmain()
{
std::cout<<"J'écrisçaavec plein de caractères spéciaux:éùàè"<<std::endl;
}
Ensuite il s’affiche dans la console :
C++
1
J'ÚcrisÞa...
Il y a donc un problème de reconnaissance des caractères spéciaux.
Explications :
Une façon de résoudre ce problème est d’utiliser cette fonction :
std::cout<<Formater("J'écrisçaavec plein de caractères spéciaux:éùàè")<<std::endl;
Maintenant on peut afficher correctement les caractères spéciaux :
Résumé :
Avec l’alphabet français on ne pouvait pas écrire de texte avec les caractères spéciaux.
Mais avec la fonction Formater() présenté, les caractères s’affichent correctement.
Les pointeurs intelligents sont une extension des pointeurs bruts et ont été inventés afin d’éviter les fuites de mémoire.
Explications :
Le principe est qu’un pointeur intelligent encapsule un pointeur brut : le pointeur habituel et le muni d’un système de libération automatique de la mémoire. Il suit le principe RAII.
C’est principalement autour de la notion de « durée de vie » qu’un pointeur tient son utilité. En effet le pointeur intelligent opère sur les actions de création, de copie, de destruction, etc… d’un objet.
Dans la version du C++11 il existe trois types différents de pointeurs intelligents.
– unique_ptr, le pointeur ne peut pas être partagé (il ne peut y avoir qu’un seul propriétaire).
Il est préférable d’utiliser ce type de pointeur plutôt que l’ancien type de pointeur (d’avant C++11) : auto_ptr. En effet auto_ptr ne dispose pas d’un système de protection de la copie !
– shared_ptr, le pointeur peut être partagée par un système de comptage deréférence.
L’idée est qu’un pointeur intelligent est automatiquement détruit lorsqu’il sort de portée du bout de code.
Voici les opérateurs surchargés d’un pointeur intelligent :
C++
1
2
3
4
5
6
template<typenameT>
classPointeurIntelligent
{
T*operator->()const;
T&operator*()const;
};
Exemple :
Une fonction retournant un pointeur : une fois retourné on ne sait pas où, quand et comment libérer la mémoire allouée par ce pointeur !
C++
1
2
3
#include <memory>
Data*getData();
Mais depuis C++11, on peut utiliser un type le pointeur intelligent nommé unique_ptr comme suit :
C++
1
2
3
#include <memory>
std::unique_ptr<Data>getData();
Il sera détruit automatiquement à la fin de la portée du bloc de code ! Comme le pointeur brut est encapsulé dans l’objet unique_ptr<> : il sera aussi détruit avec. Une de ses particularité est qu’il a toujours un unique propriétaire. Mais la fonction std::move permet d’échanger celui-ci.
Un autre type de pointeur intelligent est le shared_ptr, il peut y être adjoint plusieurs références. Lorsque la dernière référence est disjointe, le pointeur est détruit !
C++
1
2
3
#include <memory>
std::shared_ptr<Data>getData();
Pour finir, il est important de noter que l’on peut créer un objet sur le tas en utilisant std::unique_ptr !!
Résumé :
Nous avons présenté le concept et les objets des pointeurs intelligents du C++11 !
Nous allons implémenter un système de delegates comme celles existantes en C#.
Tout d’abord qu’est-ce qu’une fonction anonyme (appelée aussi fonction lambda) ?
Une fonction anonyme est tout simplement une fonction qui n’a pas de nom et qui peut être définie à la volée.
Depuis la dernière version C++11 on peut définir des fonctions anonymes.
Dans ce tutoriel nous allons apprendre à utiliser deux objets de la STL que sont : std::function et std::bind.
A noter que je me suis servi de ces delegates pour implémenter le système d’Events de cet article.
Prérequis :
– Savoir un peu lire du C++.
– Disposer d’un compilateur compatible C++11.
Explications :
Une fonction anonyme peut être définie ainsi :
C++
1
2
3
4
5
6
7
#include <functional>
// Définition d'une fonction anonyme
auto UneFonction=[](inti)->double{return2*i;};
// Appel de cette fonction anonyme
doubled=UneFonction(1);
On place des crochets au début pour annoncer une fonction lambda.
Entre parenthèses on indique les paramètres.
Le type indiqué juste après la flèche indique le type de retour de la fonction.
L’objet std::function peut être affecté par une simple fonction ou par une fonction anonyme :
C++
1
2
3
4
5
6
7
8
9
10
#include <functional>
// Une fonction toute simple
intmy_func(inta,intb){returna+b;}
// On peut affecter cette précédente fonction à un objet std::function
std::function<int(int,int)>f=my_func;
// On peut affecter directement une fonction anonyme
Parfois on a besoin simplement d’utiliser une variable ou un objet de manière temporaire, pour ensuite le ou la détruire facilement après son utilisation.
En C++, les objets résidant sur la pile sont automatiquement détruits lors de la sortie d’un bloc de portée, y compris dans le cas d’une levée d’exception ; leur destructeur est appelé avant que l’exception ne se propage.
On peut alors se servir de ce mécanisme pour détruire automatiquement un objet une fois effectué son utilisation.
Prérequis :
– Avoir quelques notions de C++
Explications :
Il s’agit de manipuler un objet local qui sera utilisé dans la portée d’un bout de code et qui sera ensuite libérée lors de sa destruction.
Système de profiling :
Exemple :
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
voidExempleAvecRAII()
{
// On acquiert une ressource (un fichier)
Fichier logFile("Log.txt");
logFile.Write("Bonjour!");
// Le fichier logFile sera automatiquement détruit
// après avoir atteint la fin de ce bloc de code.
// On n'a pas besoin d'appeler une quelconque fonction
// close()
/* On peut lever aussi une exception juste ici. */
if(logFile.writeHasFailed())
{
throwstd::exception("Erreur!");
}
}
Résumé :
Nous avons présenté le principe du RAII. Ce principe permet de simplifier les initialisation du constructeur d’un objet, sans avoir à appeler le destructeur.
Le langage C++ est le langage le plus utilisé dans le monde du développement de jeu-vidéo.
Ce petit tutoriel à pour but de vous faire réviser certaines notions de C++.
Prérequis :
– Avoir quelques notions de C++
Explications :
Le C++ est un langage orienté objet. C’est-à-dire que l’on manipule des objets en déclarant des classes au sein du code.
Qu’est-ce qu’une déclaration de fonction / classe ?
Une déclaration indique l’identificateur d’une variable et sa nature (c’est-à-dire respectivement son nom et sa nature).
Qu’est-ce qu’une définition de fonction / classe ?
Une définition implémente la structure d’une fonction ou d’une classe afin de pouvoir l’utiliser.
Commentaires :
C++
1
2
3
4
/* Ceci est un commentaire
sur plusieurs lignes */
// Ceci est un commentaire en une seule ligne
Qu’est-ce qu’une classe ?
Une classe permet de regrouper dans une même entité des données et des fonctions membres (appelées aussi méthodes) permettant de manipuler ces données.
Créer un objet à partir d’une classe :
Les opérateurs new et delete permettent de créer un objet en allouant sa quantité de mémoire.
A chaque new doit correspondre un delete, sinon il risque d’avoir une fuite de mémoire (memory leak) !
L’opérateur delete appelle le constructeur à détruire de la classe.
Qu’est-ce qu’un objet ?
Un objet est l’instance d’une certaine classe.
L’héritage :
En C++, les classes peuvent hériter d’autres classes et la relation d’héritage est exprimée à l’aide de l’opérateur de dérivation « : ».
C++
1
2
3
4
5
classChat:publicAnimal
{
public:
private:
};
L’encapsulation :
C++
1
2
3
4
5
6
7
8
9
10
11
classSprite2D
{
public:
Sprite2D();
virtual~Sprite2D();
protected:
private:
intm_iVariable;
};
– les déclarations en dessous de « public: » seront accessibles depuis n’importe quelle fonction.
– en dessous de « protected: » : pareil que private mais accessible depuis les classes
filles.
– en dessous de « private: » : accessible que depuis les fonctions de la classes.
L’opérateur sizeof :
Sert à calculer la taille de n’importe quel type de données.
On note que sizeof(char) = 1.
sizeof(x) = 1 ssix est un byte. (Un byte est la plus petite unité adressable d’un ordinateur)
Opérateurs d’entrées-sorties :
On peut écrire dans la console avec l’instruction suivante :
C++
1
2
3
#include <iostream>
std::cout<<"Une phrase."<<std:endl;
On peut écrire du code UTF-8 aussi :
C++
1
2
3
#include <iostream>
std::wcout<<"Une phrase en français."<<std:endl;
Les pointeurs :
Un pointeur désigne une adresse d’une variable d’un certain type.
C++
1
2
3
4
5
6
7
int*pEntier;// Déclare un pointeur de type int
intiEntier;
&iEntier // permet d'obtenir l'adresse de iEntier
pEntier=&iEntier;// Affectation du pointeur
Le références :
Une référence peut être vue comme un alias d’une variable. C’est un « synonyme » d’une autre variable. Une modification de l’une modifiera l’autre.
C++
1
2
inti=3;
int&i;// Un référence à la variable i
Valeur par défaut des paramètres d’une fonction :
C++
1
2
// Déclaration d'une fonction à paramètres par défaut
voidprint(longvaleur,intbase=10);
Fonctions inline :
Les fonction inline fonctionnent comme les macros.
Surcharge de fonctions :
Tas et Pile :
Tas :
Pile :
Instanciation d’une classe :
C++
1
2
Avion avion;// Sur la pile
Avion*pAvion=newAvion();// Sur le tas
Fonctions membres statiques :
– existent même s’il n’y a aucune instance de classe.
– ne peuvent accéder qu’aux membres statiques de la classe.
Les débutants en C++ sous Windows ont souvent du mal à compiler leur premier programme.
En effet pour compiler un programme il faut indiquer au compilateur où chercher ses dépendances (répertoires de fichiers d’en-têtes et de bibliothèques)
Prérequis :
– Savoir un peu écrire et lire du C++
Explications :
1) Ouvrez votre projet C++
2) Allez dans les propriétés du projet (ALT+F7)
3) Allez dans la partie « Répertoires C++ »
Vous verrez deux champs qui sont là pour indiquer où chercher vos fichiers #include et bibliothèques .lib des dépendances de votre projet.
4) Il vous reste plus qu’à sélectionner le répertoire de votre dépendance !
5) Compilez pour tester si votre projet est bien configuré !
Résumé :
Dans cet article nous avons configuré Visual C++ de façon à ce qu’il trouve les dépendances externes à votre projet afin de le compiler sans erreurs apparentes.
Parfois, étant donné le nombre faramineux de fichiers d’en-tête nécessaires pour compiler un fichier source .cpp, il peut etre nécessaire de tous les regrouper en seul fichier afin de le compiler en une seule fois.
Cette compilation accélère immédiatement la compilation des autres fichiers .cpp.
Prérequis :
– Savoir un peu lire du C++
Explications :
1) Ouvrez votre projet VC ++
2) Créez un fichier Stdafx.h où vous mettrez tous vos fichiers .h les uns à la suite des autres (comme dans la première image).
3) Créez juste un fichier Stdafx.cpp où vous metterez :
C++
1
#include "Stdafx.h"
4) Faite un clic droit sur ce dernier fichier :
5) Allez dans la partie « En-tête précompilés ». Choisissez « Création /Yc » et ajouter comme dans l’image « Stdafx.h »
6) Ensuite allez dans les propriétés du projet (ALT+F7)
7) Veillez à ce que « Stdafx.h » soit écrit et choisissez « Utilisation /Yu » :
8) Dans le fichier Stdafx.h, vous pouvez mettre tous vos fichier #include :
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "BoundingVolumeTrigger.h"
#include "Mob.h"
#include "NPC.h"
#include "TextAnnouncer.h"
#include "EventManager.h"
#include "M2Importer.h"
#include "ComputerInfo.h"
#include "CommandsManager.h"
#include "DataParameters.h"
#include "Commands.h"
#include "Actor.h"
#include "GUIScrollBar.h"
#include "GUIManager.h"
#include "GUIButton.h"
#include "GUIDraggableWindow.h"
9) Il reste plus qu’à mettre dans tous vos fichiers .cpp une seule en-tête :
C++
1
#include "Stdafx.h"
10) Compilez et admirez la vitesse !
Résumé :
Nous avons appris comment regrouper un grand nombre de fichiers d’en-tête (des #include) en un seul fichier afin de rendre la compilation beaucoup plus rapide.
En C++, avant de compiler le programme, il est possible d’effectuer certaines modifications sur le code source. Le programme effectuant ces modifications s’appelle le préprocesseur. Les commandes destinées au préprocesseur commencent toutes par # en début de ligne.
Prérequis :
Savoir les rudiments du C++.
Inclusion de fichiers sources :
On peut inclure des fichiers .h avec la directive #include :
C++
1
2
#include "EventManager.h"
#include <iostream>
Cela a pour effet d’inclure le code source du fichier à l’emplacement indiqué.
Directives du préprocesseur :
#define : permet de définir une constante globale :
C++
1
#define CAMERA_MOVE_SPEED 2.0f
#ifndef : permet de tester si la variable testée n’est pas définie
#ifdef : permet de tester si la variable testée est définie
Macro prédéfinies :
__DATE__ : date de compilation du fichier source actuel
__FILE__ : nom du fichier source actuel
__LINE__ : numéro de ligne dans le fichier source actuel
__TIME__ : heure de compilation la plus récente du fichier source actuel
__FUNCTION__ : nom de la fonction en cours
_WIN32 : défini si le programme est compilé sous Windows ou non
on utilise cette macro / raccourcis pour appeler une méthode : EVENT_MANAGER->methode()
cela permet de simplifier l’écriture du code…
Résumé :
Le patron de conception Singleton restreint l’instanciation d’une classe à un seul objet.
Il permet de simplifier l’accès global à un objet (par exemple à une classe TextureManager ou EventManager)