Archives pour la catégorie C++

Utilisation des std::array en C++11

arrays

Intro :

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 :

std::array<int, 5> a = {1, 2, 3, 4, 5};

Voilà une déclaration supplémentaire :

#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.

Utilisez Doxygen pour documenter vos programme C++ en HTML

Doxygen_logo

Intro :

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 !

Références :

– https://fr.wikipedia.org/wiki/Doxygen

– http://franckh.developpez.com/tutoriels/outils/doxygen/

– http://axiomcafe.fr/tutoriel-documenter-un-code-avec-doxygen

Convertissez rapidement vos chaînes de caractères avec swscanf

ascii

Intro :

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++ <cwchar> 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.

Le format de cette fonction est le suivant :

int swscanf (const wchar_t* ws, const wchar_t* format, ...);


Paramètres :

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 :

wchar_t sentence [] = L"Clément a 28 ans";
wchar_t str [20];
int i;

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.

Références :

– http://www.cplusplus.com/reference/cwchar/swscanf/

– https://msdn.microsoft.com/fr-fr/library/zkx076cy.aspx

Les exceptions

exception

Intro :

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 <exception>).

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 :

void UneFonction()
{
[...]
    throw std::exception("Une erreur s'est lancée !");
[...]
}

try
{
    UneFonction();
}
catch(std::exception& e )
{
    std::cout << e.what() << std::endl;
}
catch ( ... ) // traite toutes les autres exceptions
{
    std::cerr << "Erreur inconnue.\n";
}

Il existe une fonction qui permet de personnaliser la fonction d’appel lors d’une exception non gérée ou traitée : set_terminate(…)

A noter qu’on peut coupler le concept du RAII avec les exceptions (voir l’article)

Résumé :

Les exceptions offre un moyen très rapide et élégant de gérer les erreurs dans les programmes.

Références :

– http://guillaume.belz.free.fr/doku.php?id=pourquoi_le_raii_est_fondamental_en_c

– http://cpp.developpez.com/faq/cpp/?page=Utilisation-des-exceptions#Qu-est-ce-qu-une-exception

Les processus légers (threads) avec std::thread (C++11)

Intro :

Les threads sont utilisés dans ce qu’on nomme  « la programmation concurrente », 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 :

#include <iostream>
#include <thread>

void f()
{
    std::cout << "Un thread C++11" << std::endl;
}

int main()
{
    std::thread t(f);

    t.join();

    return 0;
}

 

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é :

#include <iostream>
#include <thread>

void f()
{
    std::cout<< "Un thread C++11" << std::endl;
}

int main()
{
    std::thread t(f);
    t.join();
    t.detach();

    return 0;
}

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 mutex on 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 mutex en même temps que la variable à protéger !

struct Data
{
    int var;
    std::mutex mutex;
};

 

Qu’est-ce qu’une condition ?

Une condition est 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 !

Références :

– http://www.iro.umontreal.ca/~dift1169/cours/ift1169/communs/Cours/2P/C12_1169_2P.pdf

– https://fr.wikipedia.org/wiki/Thread_(informatique)

Lire un fichier ligne par ligne

file

Intro :

Parfois on a besoin de lire ligne par ligne un fichier, par exemple pour lire un fichier de modèle 3D ou un fichier de configuration.

Prerequis :

– Savoir lire du C++

Explications :

Voici le code pour obtenir le contenu d’un fichier :

#include <fstream>

std::ifstream inFile("MonFichier.txt");

if (inFile)
{
    std::string sLigne;

    // Tant qu'on n'est pas à la fin, on lit
    while (std::getline(inFile, sLigne))
    {
        std::cout << sLigne << std::endl;
    }
}
else
{
    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().

Références :

– Openclassroom.com

Combiner des paramètres (flags) dans une déclaration enum

bits

Intro :

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 :

enum KeyModifier
{
    SHIFT,
    CTRL,
    ALT
};

Mais on aimerait les combiner avec l’opérateur booléen : | (le ou logique) de la façon suivante :

// 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 :

enum KeyModifier
{
    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)

#define BIT(x) 1 << x


Mais que fait cette dernière macro ?

Elle remplace les champs de déclarations binaires de manière simple.

enum KeyModifier
{
    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++.

Références :

– http://www.learncpp.com/cpp-tutorial/38-bitwise-operators/

Afficher des caractères accentués dans la console

Intro :

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 :

#include <iostream>

int main()
{
   std::cout << "J'écris ça avec plein de caractères spéciaux : éùàè" << std::endl;
}

 

Ensuite il s’affiche dans la console :

J'Úcris Þa...

 

console_caractères_mauvais

 

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 :

#include <Windows.h>
#include <string>

std::string Formater(const std::wstring& src)
{
    char outString[512];
    CharToOemW(src.c_str(), outString);

    return std::string(outString);
}

// Exemple d'utilisation :
std::cout << Formater("J'écris ça avec plein de caractères spéciaux : éùàè") << std::endl;

 

Maintenant on peut afficher correctement les caractères spéciaux :

console_caractères

 

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.

Références :

– http://h-deb.clg.qc.ca/Sujets/AuSecours/Afficher–Accents.html

Apprendre le C++

C++-unofficial.sh-600x600

Intro :

Il vous faut apprendre le C++ avant de concevoir des mécanismes internes à la conception de jeux vidéo.

Il existe beaucoup de tutoriels sur comment apprendre à programmer en C++ sur la toile (et en français !)

Je vous liste une série de tutoriels qui m’ont parus intéressants.

Explications :

– L’excellent tutoriel C++ de Mathieu Nebra sur openclassroom.com (anciennement Site Du Zéro) :

http://openclassrooms.com/courses/programmez-avec-le-langage-c

–  La FAQ C++ de developpez.net :

http://cpp.developpez.com/faq/cpp/

Résumé :

Sur ce site je ne vous apprend pas à utiliser le C++ ; je vous fourni cependant quelques tutoriels sur le sujet.

Les pointeurs intelligents

AymB9

Intro :

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 de ré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 :

template <typename T>
class PointeurIntelligent
{
    T* operator->() const;
    T& operator*() const;
};


Exemple :

Une fonction retournant un pointeur : une fois retourné on ne sait pas , quand et comment libérer la mémoire allouée par ce pointeur !

#include <memory>

Data* getData();

 

Mais depuis C++11, on peut utiliser un type le pointeur intelligent nommé unique_ptr comme suit :

#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 !

#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<Objet> !!

Résumé :

Nous avons présenté le concept et les objets des pointeurs intelligents du C++11 !

Références :

– https://msdn.microsoft.com/fr-fr/library/hh279674.aspx

– http://en.wikipedia.org/wiki/Smart_pointer

– http://www.informit.com/articles/article.aspx?p=2085179

Nouveautés C++11

5146902896965.jpg

Intro :

Des changements ont été ajoutés dans la nouvelle version du C++ (nommée C++11).

Tous les mots-clés en liaison avec les changements sont représentés dans l’image de l’en-tête.

Explications :

Le mot-clé / constante nullptr :

– Il remplace l’ancien « #define NULL 0  » pour définir un pointeur nul.

Le mot-clé auto :

– Il identifie automatiquement le type de la variable correspondante en fonction de l’affectation décrite. Il permet de simplifier l’écriture du code.

Exemple :

for(auto i = nombres.begin() ; i != nombres.end() ; ++i)
{
    std::cout << (*i) << std::endl;
}


decltype :

– Permet de retrouver le type de l’objet lorsque l’on a utilisé le mot-clé auto.

Variadic templates :

– Permet d’utiliser un nombre variable de spécifications de classe sur les templates.

unique_ptr<> :

– Un pointeur intelligent possédant qu’un seul (et unique) propriétaire !

Initialisation des données membres non-statique :

– Les variables membres non-statiques peuvent être définit au sein même de la déclaration de la classe.

class UneClasse
{
public:
    UneClasse() {}
 
    int a = 7;
    int b = 5;
 
private:
    std::string s{"Une chaîne"};
};


Alias de templates :

– On peut désormais utiliser le mot-clé « using » pour faire un alias à la place du classique typedef

Constructeurs délégués :

– Avec le C++11, un constructeur peut appeler un autre constructeur directement.

struct A
{
    A(int);
    A(): A(42) { } // délègue au constructeur A(int)
};


Range-based for :

– On peut parcourir un tableau, une liste, un vector, etc… avec une boucle simplifiée :


std::vector<double> v;

for (double d : v)
{
    std::cout << d << std::endl;
}

for (auto d : v)
{
    std::cout << d << std::endl;
}

for (auto& d : v)
{
    ++d;
}

for (auto const& d : v)
{
    ++d;
}

std::map<int, std::string> m;

for (auto element : m)
{
    printf("%d\n", element.first);
    printf("%s\n", element.second);
}


Les fonctions lambda : 

– La syntaxe est : [ liste de capture ] ( paramètres ) retour { code }

Exemple :

[](int element) {
    cout << element << endl;
}

On peut passer une fonction anonyme à une variable :

std::function<int (int, int)> addition = [](int x, int y) -> int {
    return x + y;
};


Les énumérations fortement typées :

Dorénavant les numérations peuvent être fortement typées ainsi :

// On rajoute le mot-clé "class"
enum class Direction { Haut, Droite, Bas, Gauche }; // Se comporte comme des attributs !

//  On est obligé de spécifier la classe d'origine "Direction::"
Direction direction = Direction::Haut;

// Pour afficher sa valeur entière on doit convertir :
std::cout << static_cast<int>(direction) << std::endl;


Bug template corrigé :

// Avant :
std::vector<std::vector<int> > nombres;

// Après :
std::vector<std::vector<int>> nombres;


Les listes d’initialisateurs :

std::vector<int> nombres = { 1, 2, 3, 4, 5 };
std::map<int, std::string> nombres = { { 1, "un" }, { 2, "deux" }, { 3, "trois" } };


Les tableaux à taille fixe :

std::array<int, 5> tableauFixe;


Tuple :

Mémoire locale pour un thread :

– Avec le mot-clé std::thread_local, on peut spécifier une variable locale à un thread !

Sizeof sur les attributs de classes :

– On peut désormais savoir la taille qu’occupe un attribut d’une classe.

Références :

– http://cpp.developpez.com/redaction/data/pages/users/gbdivers/cpp11/

– http://fr.wikipedia.org/wiki/C%2B%2B11

– http://openclassrooms.com/courses/introduction-a-c-2011-c-0x

Fonctions delegates avec std::function et std::bind

 

5146902896965.jpg

Intro :

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 :

#include <functional>

// Définition d'une fonction anonyme
auto UneFonction = [](int i) -> double { return 2 * i; };

// Appel de cette fonction anonyme
double d = 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 :

#include <functional>

// Une fonction toute simple
int my_func(int a, int b) { return a + 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
std::function<int (int)> UneFonction = [](int i) -> double { return 2 * i; };

On met entre crochets : le type de retour et le type des paramètres entre parenthèses.

 

L’objet std::bind permet de lier un ou plusieurs paramètres de manière préétablis à un objet std::function :

#include <functional>
#include <iostream>

// Une fonction standard
void Afficher(std::string& texte)
{
    std::cout << "TEXTE: " << texte << std::endl;
}

// On peut lier à cette précédente fonction un paramètre prédéfini
std::function <void ()> f = std::bind(Afficher, "Du texte !!");

f(); // Affiche "TEXTE : Du texte !!"

 

On peut faire spécifier ultérieurement un paramètre avec std::placeholder::_* :

#include <functional>
#include <iostream>
 
int multiply(int a, int b)
{
    return a * b;
}
 
int main()
{
    auto f = std::bind(multiply, 5, std::placeholders::_1);
    for (int i = 0; i < 10; i++)
    {
        std::cout << "5 * " << i << " = " << f(i) << std::endl;
    }

    return 0;
}

 

On peut stocker des std::function dans un std::vector :

#include <functional>
#include <vector>

std::vector<std::function<double (int)>> fs;

auto UneFonction = [](int i) -> double { return 2 * i; };

fs.push_back(UneFonction);

// On peut invoquer ces fonctions par une boucle automatique
for (auto& f : fs)
{
    f();
}


Résumé :

Nous avons appris comment définir des fonctions delegates comme celle disponibles en C#.

Références :

– http://fr.cppreference.com/w/cpp/utility/functional/function

– http://fr.cppreference.com/w/cpp/utility/functional/bind

– http://oopscenities.net/2012/02/24/c11-stdfunction-and-stdbind/

– http://openclassrooms.com/courses/introduction-a-c-2011-c-0x

RAII (Resource Acquisition Is Initialization)

02_clipmaps_02

Intro :

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 :

void ExempleAvecRAII()
{
  // 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())
  {
      throw std::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.

Récapitulatif C++

C++-unofficial.sh-600x600

Intro :

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 :


/* 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 « : ».

class Chat : public Animal
{
public:
private:
};

L’encapsulation :

class Sprite2D
{
public:
    Sprite2D();
    virtual ~Sprite2D();

protected:

private:
    int m_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 ssi x 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 :


#include <iostream>

std::cout << "Une phrase." << std:endl;

On peut écrire du code UTF-8 aussi :


#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.


int* pEntier; // Déclare un pointeur de type int

int iEntier;

&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.

int i = 3;
int& i; // Un référence à la variable i

Valeur par défaut des paramètres d’une fonction :


// Déclaration d'une fonction à paramètres par défaut
void print(long valeur, int base = 10);

Fonctions inline :

Les fonction inline fonctionnent comme les macros.

Surcharge de fonctions :

Tas et Pile :

Tas :

Pile :

Instanciation d’une classe :

Avion avion; // Sur la pile
Avion* pAvion = new Avion(); // 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.

Exceptions :

Moyen de gérer les erreurs dans un programme C++.

Throw : // Le mot-clé pour lever une exception.

/* Bloc try d'une gestion d'exception */
try
{
    // instructions pouvant déclencher des exceptions
    // dérivant de std::exception
}
catch ( const std::exception & e )
{
    std::cerr << e.what();
}

Bien configurer un projet Visual C++

index

[rajouter utf-8]

Intro :

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)

proprieties_2

3) Allez dans la partie « Répertoires C++ »

repertoires_1

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 !

repertoires_2

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.

 

Les fichiers d’en-têtes précompilés (Visual C++)

PrecompiledHeaders

Intro :

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 :

#include "Stdafx.h"

4) Faite un clic droit sur ce dernier fichier :

proprieties_4

5) Allez dans la partie « En-tête précompilés ». Choisissez « Création /Yc » et ajouter comme dans l’image « Stdafx.h »

proprieties_5

6) Ensuite allez dans les propriétés du projet (ALT+F7)

proprieties_2

7) Veillez à ce que « Stdafx.h » soit écrit et choisissez « Utilisation /Yu » :

proprieties

8) Dans le fichier Stdafx.h, vous pouvez mettre tous vos fichier #include :

#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 :

#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.

Utilisation du préprocesseur

224706

Intro :

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 :


#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 :


#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

__VA_ARGS__ : voir cette article

Pragma :

Pour supprimer des warnings que vous jugez inutiles :


#pragma warning (disable : 4018 )

Pour spécifier au compilateur que le fichier ne doit être inclu qu’une fois :


#pragma once

Pour spécifier au compilateur de lier une librairie donnée :

#pragma comment(lib, "Newton.lib")

 

Résumé :

Nous avons énuméré les principales fonctionnalités du préprocesseur en C++.

Le Singleton

Intro :

Le Singleton est un patron de conception.

Il est utile lorsque l’on a besoin d’utiliser et d’instancier un seul objet au lieu le passer paramètre après paramètre et fonction par fonction.

En somme il permet d’accéder directement dans tout le programme un objet quelconque (appelé « Singleton »).

Explications / exemples :

Cas sans le Singleton :


// on passe le paramètre fonction après fonction
void une_foncton(Object* obj)
{
    une_autre_fonction(obj);
}

// et encore un paramètre de passé...
void une_autre_fonction(Object* obj)
{
    std::string sName = obj->getName();

    std::cout << sName << std::endl;
}

Cas avec le Singleton :

On accède directement à l’objet sans avoir à le passer paramètre par paramètre :


void une_autre_fonction()
{
    Object->getSingleton()->getName();
}

Donc  :


// Cette fonction n'est plus utile
void une_foncton(Object* obj) 


Voici le fichier d’en-tête / d’include :


#ifndef SINGLETON_H
#define SINGLETON_H

#define SAFE_DELETE(p) if (p) { delete (p); (p) = NULL; }

template <typename T>
class Singleton
{
public:
    Singleton()
    {
        assert(!ms_instance);

        ms_instance = static_cast<T*>(this);
    }

    ~Singleton () {}

    static T* getSingletonPtr()
    {
        assert(ms_instance);

        return ms_instance;
    }

    static void destroy()
    {
        SAFE_DELETE(ms_instance);
    }

    static bool IsCreated()
    {
        return ms_instance != nullptr;
    }

private: // Opération interdite
    Singleton(const Singleton<T> &);
    Singleton& operator=(const Singleton<T> &);

private:
    static T* ms_instance;
};

#endif

 

Utilisation :

Dans le fichier include : EventManager.h


class EventManager : public Singleton<EventManager>
{
    // Suite de votre code...
};

Dans le fichier cpp : EventManager.cpp

on doit mettre tout en haut de ce fichier :


template<> EventManager* Singleton<EventManager>::ms_instance = NULL;


Exemple d’utilisation :


GlobalSystems::GlobalSystems()
{
    m_pInputManager = InputManager::getSingletonPtr();
    m_pTextAnnouncer = TextAnnouncer::getSingletonPtr();
    m_pCameraManager = CameraManager::getSingletonPtr();
    m_pConsole = Console::getSingletonPtr();
}

Astuces et conseils :

warning

1 – N’oubliez surtout pas de créer votre objet avant son utilisation en appelant :


new NomDeLaClasse();

sinon vous obtiendrez un bug de pointeur non initialisé ! (segfault)

2 – Utiliser plusieurs macro #define de la sorte :


#define EVENT_MANAGER EventManager->getSingleton()

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)

Fonction à nombres variables d’arguments

Intro :

Il peut être utile de passer un nombre indéfini de paramètres à une fonction.

Il faut inclure au minimum un paramètre obligatoire. Ensuite on peut passer un nombre indéfini d’arguments à la fonction.

Exemple  :

    void nomFonction(int parametreA, ...);

Les trois petits points (…) indique au compilateur que la fonction peut prendre plusieurs arguments.

Il faut inclure <stdarg.h> pour accéder aux fonctions et macros de manipulation des paramètres variables.

On doit indiquer en paramètre le nombre d’arguments passés par cette fonction ! (ici « int n »)

#include <stdarg.h>

void f (int n, ...)
{
   va_list va;
   va_start (va, n);

   for (unsigned int i = 0; i < n; i++)
   {
      char* sArgName = va_arg(va, char*);
   }

   va_end (va);
}

Ici on récupère des variables de type char* (chaine de caractères).

Résumé :

Les fonctions qui peuvent prendre un nombre variable d’arguments ne sont pas toujours utilisées mais peuvent toujours être utiles.

Macro avec multiple passage d’arguments

Intro :

Parfois il est nécessaire d’utiliser une macro avec nombre variable d’arguments / paramètres

#define fonction(param1, ...) printf(param, ##__VA_ARGS__);

Utilisation :

Ce sont des macros similaires aux fonctions qui contiennent un nombre variable d’arguments.

On peut utiliser une macro avec un nombre variables d’arguments :


ma_macro("test", "du_texte", "un_commentaire", "une_histoire");

Le mot-clé __VA_ARGS__  sert à remplacer les occurences de paramètres dans la fonction appelée.

Résumé :

Les macros avec nombre variable d’arguments peuvent toujours trouver leur utilité même s’il ne sont pas très utilisées.

Références :

https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html

Astuces pour bien coder en C++

C++-unofficial.sh-600x600

Intro :

Voici quelques astuces qui vous permettrons de mieux vous organiser dans l’étape d’écriture de votre code.

Safe Delete :

Pour détruire correctement vos objets instanciés, vous pouvez utiliser ces macros :


#define SAFE_DELETE(a) { delete (a); (a) = nullptr; }
#define SAFE_RELEASE(x) { x->Release();  (x) = nullptr; }

SAFE_DELETE(a);
SAFFE_RELEASE(x);


Fichier de déclaration de defines :

Utiliser un fichier include (Defines.h) où vous mettrez toutes vos déclarations de defines.
Par principe il vaut mieux  regrouper ensemble tout ce qui se ressemble.


#define FIRST_PERSON_CAMERA_SPEED 3.0f
#define OGRE_RENDER_SYSTEM_NAME "Direct3D9 Rendering Subsystem"
#define TIME_FADE_ANNOUNCE 2.0f
#define GAME_VERSION_REVISION "2323"
#define GAME_VERSION_MINOR "1"
#define GAME_VERSION_MAJOR "0"


Commentaires :

En général il vaut mieux bien nommer correctement les noms des variables et des fonctions
plutôt que de mettre des commentaires partout.

« Really good code comments itself » :

Sortie de texte pour débugger :

Sous Visual Studio on peut utiliser :

OutputDebugString(_T("text"));

Cela permettra au programme d’écriture sur la sortie debug de VC++.


En-tête :

Pour expliquer et situer le rôle d’un fichier source vous pouvez créer une en-tête dans chaque fichier :


//----------------------------------------------------
// Auteur : Clément Profit
// Nom du fichier : Console.cpp
// Date de création : Juillet 2014
// Description : Une console quake-like avec commandes et
// appels d'events
//----------------------------------------------------

Pour expliquer et situer le rôle d’une méthode :


//--------------------------------------------------
// EventManager::Trigger
//--------------------------------------------------

 

Principes :

1 – ne jamais arrêter son travail quand le programme ne fonctionne pas ou lorsqu’il ne compile pas. En effet lors de la reprise ultérieure de votre travail vous aurez pas de bug(s) à corriger que vous aurez oubliés de résoudre.

2 – pensez à abuser l’utilisation des macros lorsque votre code devient répétitif.

Macro prédéfinies :

Nom de la fonction courante :


#if defined(_MSC_VER)
    #define CURRENT_FUNCTION_NAME __FUNCTION__
#elif defined(__GNUC__) && (__GNUC__ >= 3 )
    #define CURRENT_FUNCTION_NAME __PRETTY_FUNCTION__
#else
    #define CURRENT_FUNCTION_NAME "Unknown function"
#endif

Pour le reste voir l’article Préprocesseur

Assertions :

Utiliser ces assertions un peu partout dans votre code.
L’instruction asm { int 3 } indiquera au débogueur un point d’arrêt près de l’assertion en cause.


#ifndef ASSERT_H
#define ASSERT_H

#ifndef NDEBUG

#define AssertMsg(expr, msg) if (!(expr)) { std::wout << (#expr << " " << __FILE__ << " " << __LINE__ << " " << CURRENT_FUNCTION_NAME << " " << msg << std::endl); __asm { int 3 } }
#define AssertNULLPointer(pointer) if (pointer == NULL) { AssertMsg(pointer, #pointer " == NULL"); __asm { int 3 } }
                    
#else //! non-debug

#define AssertMsg(expr, msg) fastprint(msg);
#define SKIP_IF_FAILED_AssertMsg(expr, msg) if (expr)
             
#endif
                                             
#endif

Notations :

Utiliser la notation hongroise pour noter vos variables et vos noms de fonctions :

g : utiliser pour les variables globales – g_counter
m : utiliser pour les variables membres – m_counter
p : utiliser pour les variables pointeurs – m_pActor
V : utiliser pour les fonctions virtuelles – VDraw()
I : utiliser pour les classes interfaces – class IDrawable

Exemple :


Ogre::OverlayManager* m_pOverlayManager;
Ogre::Overlay* m_pOverlayConsole;
Ogre::OverlayContainer* m_pPromptImage;

std::string m_sCurrentCommmandLine;
std::string m_sPrompt;

float m_fScrollYOffset;
float m_fHeight;
float m_fWidth;
float m_fCharWidth;

Les différents types de style de codage peuvent être source de conflit entre programmeurs.

L’important est d’être régulier dans la beauté de la syntaxe,
peu importe quel style / norme de codage vous employez.

Utiliser des raccourcis pour vos classes singletons :


#define EVENT_MANAGER EventManager::getSingletonPtr()


Macro fastprint(texte) :

Pour imprimer du texte ou une variable rapidement (la macro supporte du texte « wide string » c’est-à-dire supportant l’alphabet français contrairement à printf ou à std::cout).


#define fastprint(msg) std::wcout << #msg " = " << msg << std::endl;

 

Macro debugprint(texte) :

Pour déboguer vos applications on se sert de cette macro pour afficher facilement une variable

#define debugprint(msg) std::cout << msg << " = " << std::endl;

(todo mulitples paramètres)

 

Constructeur :

Ne jamais faire échouer l’initialisation d’un objet dans son constructeur.
Définissez une méthode bool Initialize() à la place (qui peut échouée quant-à-elle) que vous appelez juste après le « new » et qui renvoit un booléen indiquant si ou non l’initialisation de l’objet à échouée.

DXTrace :

Pour afficher éventuellement une erreur DirectX, on peut utiliser cette macro :

#if defined(DEBUG) | defined(_DEBUG)
    #ifndef HR
    #define HR(x)                                      \
    {                                                  \
        HRESULT hr = x;                                \
        if(FAILED(hr))                                 \
        {                                              \
            DXTrace(__FILE__, __LINE__, hr, #x, TRUE); \
        }                                              \
    }
    #endif

#else
    #ifndef HR
    #define HR(x) x;
    #endif
#endif

 

Const correctness  :

 

Faire une pause dans le programme :


#define WAIT_PROCESS int i; std::cin >> i; // Ne pas utiliser system("pause"); !

 

Utilisation de la fenêtre Outline de Visual Studio C++ :

Il est aisé de naviguer à travers les méthode de vos classe avec cette fenêtre.

outlines

 

Bien choisir la taille de ses types :

Utilisez uint16 à la place de uint32 quand cela est possible, cela réduira la taille consommée par votre programme à l’exécution. Vous pouvez continuer à utiliser uint32 lorsque que la variable est utilisée temporairement (par exemple dans les boucles), en effet il n’est pas important d’utiliser des variables de type uint16 sur la pile car elles sont détruites tout de suite après la portée, il faut utiliser des uint16 uniquement sur le tas.


// Après avoir ignorer un bloc de code on nous propose la reprise du programme en cours
#define SKIP_IF_FAILED_AssertMsg(expr, msg) if (!(expr)) \
FailedAssertMessageBox(#expr, __FILE__, __LINE__, CURRENT_FUNCTION_NAME, msg, true); \
else

Résumé :

Ces méthodes permettent de faciliter le codage de vos programme et de
rendre le code plus clair et plus joli.