Quelques raccourcis :

Université de Sherbrooke, développement du jeu vidéo, COA

Vous trouverez ici quelques documents et quelques liens pouvant, je l'espère, vous être utiles.

Les documents qui vous sont fournis ici le sont pour vous rendre service.

Je travaille très fort sur chacun de mes cours. Veuillez ne pas vendre (ou donner) les documents que je vous offre ici à qui que ce soit sans mon consentement. Si des abus surviennent, je vais cesser de rendre ce matériel disponible à toutes et à tous.

Si ces documents vous rendent service, faites-le moi savoir. Mon adresse de courriel est disponible via la page où on trouve mon horaire.

Si vous êtes curieuses ou curieux de voir un vieil examen (celui de la cohorte 02!), pour voir le style de question que vous pouvez rencontrer, voir UdeS-COA--Controle-Final--Cohorte-02.pdf (mais ne vous basez pas sur les questions pour le contenu, qui évolue à chaque année)

Plan de cours

À propos des séances en classe

Les quelques sections qui suivent réfèrent à ce que nous avons fait (ou allons faire) en classe cette session.

Séance

Date

Détail

S00

Vendredi 7 septembre 13h-16h

Au menu :

  • Brève présentation du plan de cours
  • Discussion de ce que signifie être OO, selon vos divers points de vue et vos expériences préalables. C'est un sujet complexe en soi et simplement en discuter est un terreau fertile pour faire réagir les neurones
  • Jongler avec un petit programme de devinette, pour dérouiller nos articulations et notre cerveau....

En vue de notre prochaine rencontre, je vous ai proposé de vous amuser avec le problème de la rédaction d'un jeu de Mastermind.

S01

Vendredi 14 septembre 13h-16h

Au menu :

Si vous souhaitez faire des exercices, dans les notes de cours :

  • Les templates sont discutés une première fois dans POO – Volume 02, pp. 11-31
  • Une brève introduction à la bibliothèque standard est proposée dans POO – Volume 02, pp. 50-95
  • Une introduction aux foncteurs est proposée dans POO – Volume 02, pp. 152-173
  • Une introduction aux λ est proposée dans POO – Volume 02, pp. 180-193
  • Une introduction aux singletons est proposée dans POO – Volume 02, pp. 222-241
  • La génération de nombres pseudoaélatoires est proposée dans POO – Volume 02, pp. 116-119

Pour vous donner une idée de ce à quoi la matière d'aujourd'hui peut servir, imaginez ceci :

// ...
class Objet3D {
   // ...
public:
   virtual void Draw(IDirect3DDevice9 *device) = 0;
   virtual ~Objet3D() = default;
   // ...
};
class Dessiner {
   IDirect3DDevice9 *dev;
public:
   Dessiner(IDirect3DDevice9 *dev) : dev{ dev } {
   }
   void operator()(const Object3D *p) const {
      p->Draw(dev);
   }
};
#include <vector>
#include <algorithm>
int main() {
   using namespace std;
   vector<Object3D*> v;
   IDirect3DDevice9 *device = ...
   //
   // remplir v...
   //
   // Tout afficher avec un foncteur (exemple)
   //
   for_each(begin(v), end(v), Dessiner{ device });
   // Tout libérer avec une λ (exemple)
   for_each(begin(v), end(v), [](Objet3D* p) {
      delete p;
   });
   // ... on peut faire mieux encore ;)
}

Comme vous pouvez le voir, afficher un monde (ici : les objets 3D vers lesquels pointent les éléments du vecteur v) et le nettoyer (ici, on suppose que les pointés peuvent être supprimés par delete, mais il y a des alternatives) devient tout simple quand on sait s'exprimer à l'intérieur des idiomes de notre langage.

Autre exemple simple mais sympathique : supposons un jeu où il y a des monstres et où il faut périodiquement filtrer ceux qui sont morts. Supposons que Monstre soit à peu près comme suit :

class Monstre {
   // ...
public:
   bool est_mort() const;
   // ...
   virtual ~Monstre();
};

... donc que Monstre soit une classe polymorphique telle que ce sont principalement ses dérivés que nous utilisons en pratique. Ainsi, une fonction qui filtre les monstres morts d'un vector<Monstre*> pourrait être :

vector<Monstre*> filtrer_morts(vector<Monstre*> v) {
   // [begin(v),p) est une séquence de non-morts, [p,end(v)) est une séquence de morts
   auto p = partition(begin(v), end(v), [](const Monstre *p) { return !p->est_mort(); });
   for_each(p, end(v), [](const Monstre *p) { delete p; }); // destruction des morts
   v.erase(p, end(v)); // élimination des éléments détruits
   return v;
}

Essayez de programmer cette fonction sans recours aux algorithmes standards et aux λ. Vous constaterez sans doute que le problème n'est pas banal...

S02

Mercredi 19 septembre 9h-12h

Au menu :

Si vous souhaitez faire des exercices, dans les notes de cours :

  • L'héritage privé est expliqué dans POO – Volume 01, pp. 55-66
  • Une introduction aux objets opaques, aux interfaces et aux fabriques (avec un survol de l'idiome pImpl) est proposée dans POO – Volume 02, pp. 136-151
  • Les λ-expressions sont décrites dans POO – Volume 02, pp. 180-193

s/o

Du 24 au 28 septembre

Cette semaine, je serai à CppCon 2018. Vous pourrez me suivre (à travers ../../Sujets/Orthogonal/cppcon2018.html) si vous le souhaitez.

S03

Vendredi 5 octobre 13h-16h

Au menu :

  • Q01
  • Discussion des accès aux flux en C++
  • Retour sur certains aspects de la séance S02 :
    • retour sur std::unique_ptr
    • présentation de std::make_unique()
    • premier contact avec le mot clé friend et son rôle, à la fois dans l'encapsulation et la définition de meilleures interfaces
    • premier contact (sommaire) avec la sémantique de mouvement
  • Si le temps le permet :
    • conception d'un tableau dynamique de int, pour explorer diverses ramifications (pas toujours évidentes) de la conception d'un tel conteneur
    • présentation de l'idiome d'affectation sécuritaire

Pour celles et ceux qui le souhaitent, le Tableau modélisant un (simpliste et encore incomplet) tableau dynamique d'entiers que nous avons écrit ressemblait à ceci :

#include <cstddef> // std::size_type
#include <algorithm>
class Tableau {
public:
   using value_type = int;
   using size_type = std::size_t;
   using pointer = value_type*;
   using const_pointer = const value_type*;
   using reference = value_type&;
   using const_reference = const value_type&;
private:
   pointer elems{};
   size_type nelems{},
             cap{};
public:
   size_type size() const noexcept {
      return nelems;
   }
   bool empty() const noexcept {
      return !size();
   }
   size_type capacity() const noexcept {
      return cap;
   }
private:
   bool full() const noexcept {
      return size() == capacity();
   }
public:
   using iterator = pointer;
   using const_iterator = const_pointer;
   iterator begin() noexcept {
      return elems;
   }
   const_iterator begin() const noexcept {
      return elems;
   }
   const_iterator cbegin() const noexcept {
      return begin();
   }
   iterator end() noexcept {
      return begin() + size();
   }
   const_iterator end() const noexcept {
      return begin() + size();
   }
   const_iterator cend() const noexcept {
      return end();
   }
   Tableau() = default;
   Tableau(size_type n, const_reference val)
      : elems{ new value_type[n] }, nelems{ n }, cap{ n } {
      std::fill(begin(), end(), val);
   }
   Tableau(const Tableau &autre)
      : elems{ new value_type[autre.size()] }, nelems{ autre.size() }, cap{ autre.size() } {
      std::copy(autre.begin(), autre.end(), begin());
   }
   ~Tableau() {
      delete [] elems;
   }
   void swap(Tableau &autre) noexcept {
      using std::swap;
      swap(elems, autre.elems);
      swap(nelems, autre.nelems);
      swap(cap, autre.cap);
   }
   Tableau& operator=(const Tableau &autre) {
      Tableau{ autre }.swap(*this);
      return *this;
   }
   reference operator[](size_type n) noexcept {
      return elems[n];
   }
   const_reference operator[](size_type n) const noexcept {
      return elems[n];
   }
   bool operator==(const Tableau &autre) const noexcept {
      return std::equals(begin(), end(), autre.begin(), autre.end());
   }
   bool operator!=(const Tableau &autre) const noexcept {
      return !(*this == autre);
   }
   void push_back(const_reference val) {
      if(full) grow();
      elems[size()] = val;
      ++nelems;
   }
private:
   void grow() {
      auto nouv_capacite = capacity()? capacity() * 2 : 1'024;
      auto p = new value_type[nouv_capacite];
      std::copy(begin(), end(), p);
      delete [] elems;
      elems = p;
      cap = nouv_capacite;
   }
};

Si vous souhaitez faire des exercices, dans les notes de cours :

  • L'amitié (friend) est décrite dans POO – Volume 02, pp. 251-257
  • Les pointeurs intelligents sont discutés dans POO – Volume 02, pp. 120-135. Notez que c'est un sujet vaste sur lequel nous reviendrons à plusieurs reprises
  • L'essentiel de ce qui a été présenté aujourd'hui se trouve sur les liens susmentionnés. Toutefois, la semaine prochaine, le coeur de notre réflexion se trouvera dans le document POO – Volume 02, pp. 96-115 (voir en particulier la page 111 pour la sémantique de mouvement, qui risque de surprendre certains d'entre vous)

Pour un exemple d'utilisation de std::unique_ptr avec une fonction de nettoyage du pointé qui soit différente de l'opérateur delete (qui est le comportement par défaut), voici un petit exemple opérationnel. J'ai utilisé un truc qui expose un Release() bidon pour me coller un peu à ce que vous utilisez avec DirectX, mais sachez que ce qui se fait avec DirectX (et COM en général) est plus subtil que ce que laisse entendre cet exemple :

#include <iostream>
#include <memory>
using namespace std;
struct ID3DMachinChouette {
   virtual int fonction_tres_importante() = 0;
   virtual void Release() = 0;
   friend void relacher(ID3DMachinChouette *);
protected:
   virtual ~ID3DMachinChouette() = default;
};
class MachinChouette : public ID3DMachinChouette {
   //
   // Remarquez: toutes les méthodes sont privées. Pourtant,
   // ça fonctionne. Pourquoi?
   //
   int fonction_tres_importante() override {
      return 3; // calcul très savant, évidemment
   }
   void Release() override {
      delete this; // urg! Mais propre et légal
   }
};
void relacher(ID3DMachinChouette *p) {
   if (p) p->Release();
}
auto creer_machin_chouette() {
   return unique_ptr<ID3DMachinChouette, void(*)(ID3DMachinChouette*)>{new MachinChouette, relacher};
}
int main() {
   auto p = creer_machin_chouette();
   cout << p->fonction_tres_importante() << endl;
}

Le destructeur de p, qui est une instance du type unique_ptr<ID3DMachinChouette> et mène en fait vers un MachinChouette, est responsable de détruire le pointé, et le fait à l'aide d'un appel à relacher() tel que nous le lui avons demandé.

Notez que j'ai utilisé auto pour le type de p. Ceci signifie « p est du type de l'expression utilisée pour l'initialiser ». Dans ce cas, puisque p est initialisé par ce que retourne creer_machin_chouette(), alors son type est :

unique_ptr<ID3DMachinChouette, void(*)(ID3DMachinChouette*)>

Ce qui rend cette écriture lourde est le fait que nous utilisons une fonction comme outil de nettoyage, et que le type des pointeurs de fonctions n'est pas le plus élégant que nous ait légué le langage C. Une alternative utilisant un foncteur dont l'opérateur () serait générique nous donnerait l'écriture suivante (plus élégante, vous en conviendrez). Les modifications sont indiquées à même les commentaires :

#include <iostream>
#include <memory>
using namespace std;
struct ID3DMachinChouette {
   virtual int fonction_tres_importante() = 0;
   virtual void Release() = 0;
   friend struct Relacher; // <-- ICI
protected:
   virtual ~ID3DMachinChouette() = default;
};
class MachinChouette : public ID3DMachinChouette {
   //
   // Remarquez: toutes les méthodes sont privées. Pourtant,
   // ça fonctionne. Pourquoi?
   //
   int fonction_tres_importante() override {
      return 3; // calcul très savant, évidemment
   }
   void Release() override {
      delete this; // urg! Mais propre et légal
   }
};
struct Relacher { // <-- ICI (toute la classe)
   template <class T>
      void operator()(T p) const {
         p->Release();
      }
};
auto creer_machin_chouette() { // <-- ICI
   return unique_ptr<ID3DMachinChouette, Relacher>{ new MachinChouette }; // <-- ICI
}
int main() {
   auto p = creer_machin_chouette();
   cout << p->fonction_tres_importante() << endl;
}

Dans ce cas, même la construction du unique_ptr dans creer_machin_chouette() est plus légère, du fait que seul une instance de Relacher peut servir à titre de nettoyeur, et du fait que Relacher a un constructeur par défaut (tout à fait suffisant).

S04

Vendredi 12 octobre 13h-16h

Au menu :

Si vous souhaitez faire des exercices, dans les notes de cours :

  • Le coeur de notre réflexion se trouvera dans le document POO – Volume 02, pp. 96-115

S05

Mercredi 17 octobre 9h-12h

Au menu :

à déterminer

S06

Mercredi 24 octobre 9h-12h

Au menu :

à déterminer

S07

Vendredi 26 octobre 13h-16h

Au menu :

à déterminer

S08

Vendredi 2 novembre 13h-16h

Au menu :

à déterminer

s/o

Du 5 au 9 novembre

Cette semaine, je devrais normalement être à la rencontre de WG21, mais cela reste à confirmer

S09

Vendredi 16 novembre 13h-16h

Au menu :

à déterminer

S10

Vendredi 23 novembre 13h-16h

Au menu :

à déterminer

S11

Mercredi 28 novembre 9h-12h

Au menu :

à déterminer

S12

Vendredi 30 novembre 13h-16h

Au menu :

à déterminer

S13

Mercredi 5 décembre 9h-12h

Au menu :

à déterminer

S14

Mercredi 12 décembre 13h-16h

Chic examen plein d'amour

« S15 »

 

Semaine de finalisation et de présentation du projet, avec présentation vendredi 21 décembre 13h-16h

Code des cas sous étude dans les notes de cours

Ce qui suit vous est gracieusement offert dans le but de vous épargner une recopie pénible d'exemples et de tests proposés dans les notes de cours.

Exercice à faire suite à la séance S00

L'exercice proposé est celui de la construction d'un petit jeu de devinette

Exercice à faire suite à la séance S01

L'exercice proposé est celui de la construction d'un petit jeu de Mastermind

Résultats des questions quasi-hebdomadaires

Les moyennes des résultats obtenus aux questions quasi-hebdomadaires pour la session A2018 suivent. Notez que l'écart-type n'est pas significatif étant donné la pondération des questions (sur huit points, un point de différence représente , ce qui bousille quelque peu cette composante statistique).

 Question   Séance 
Q00 S??
Q01 S??
Q02 S??
Q03 S??
Q04 S??
Q05 S??
Q06 S??
 :
(cinq meilleurs) :

Petite remarque sur les moyennes par question de sur la moyenne globale telles que présentées ici : je n'ai chaque fois tenu compte que des participant(e)s au minitest, excluant les absent(e)s ce jour-là (ce qui explique les variations dans la colonne du ) ce qui signifie que la moyenne globale qui servira à évaluer la note finale sera, règle générale, un peu plus faible que ce que vous voyez ici (les absents à chaque minitest auront alors zéro pour ce minitest). Ce qui m'intéresse ici est de montrer les résultats pour celles et ceux qui ont effectivement répondu aux questions.

Consignes des travaux pratiques

Les consignes des travaux pratiques en lien avec le cours sont ci-dessous.

Travail Consignes Date de remise

TP00

Ici

S06

TP01

Ici

S12

Consignes du TP00

Les consignes viendront plus tard cette session.

Vous pourrez me remettre le document sous forme imprimée ou par courriel. Je vous lirai par courrier électronique sur mon adresse @USherbrooke.ca.

Date de remise : début de la séance S06.

Prenez soin de donner un nom significatif à votre document, pour éviter une accumulation de documents nommés TP00 (si votre équipe se nomme Les Vlimeux, par exemple, préférez TP00-Vlimeux à TP00 tout court).

Consignes du TP01

Les consignes viendront plus tard cette session.

Vous pourrez me remettre ce document sous forme imprimée (≈5-8 pages par personne, disons, à moins que vous n'ayez beaucoup à raconter...) ou par courriel. Je vous lirai par courrier électronique sur mon adresse @USherbrooke.ca.

Ensuite, vous serez libres pour le temps des Fêtes!

Date de remise : début de la séance S12.

Prenez soin de donner un nom significatif à votre document, pour éviter une accumulation de documents nommés TP01 (si votre équipe se nomme Les Snorros, par exemple, préférez TP01-Snorros à TP01 tout court).


Valid XHTML 1.0 Transitional

CSS Valide !