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

1 septembre (PM)

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

7 septembre (AM)

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. 49-90
  • Une introduction aux foncteurs est proposée dans POO – Volume 02, pp. 150-171
  • Une introduction aux λ est proposée dans POO – Volume 02, pp. 178-191
  • Une introduction aux singletons est proposée dans POO – Volume 02, pp. 220-239
  • La génération de nombres pseudoaélatoires est proposée dans POO – Volume 02, pp. 114-117

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)); // éliminations 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

8 septembre (PM)

Au menu :

  • Q00
  • Retour sur la question de l'héritage privé (appliqué avec l'idiome d'objets incopiables) et sur les singletons
  • Survol de la syntaxe et des possibilités offertes par les λ-expressions
  • Quelques applications des fabriques et ses interfaces dans une optique d'opacité, d'isolation du code client et du code serveur
  • Premiers contacts avec le mot clé friend et son rôle, à la fois dans l'encapsulation et la définition de meilleures interfaces
  • Premiers contacts avec les idiomes pImpl (voir aussi ceci) et RAII

Petit bonbon à la toute fin : premier contact avec la sémantique de mouvement.

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. 134-149
  • L'amitié (friend) est décrite dans POO – Volume 02, pp. 249-255
  • Les λ-expressions sont décrites dans POO – Volume 02, pp. 178-191

S03

14 septembre (AM)

Au menu :

  • Q01
  • Premier contact avec std::unique_ptr
  • Discussion des accès aux flux en C++. Notez que bien que le lien qui précède n'en parle pas, nous discuterons alors sans doute des istreambuf_iterator<char> pour la consommation de bytes non formatés d'un flux binaire
  • 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

En fin de séance, si le temps le permet : introduction aux considérations propres au design d'un tableau dynamique générique (version « vanille »).

Oui, je sais que std::vector existe mais l'idée est de voir, de comprendre les enjeux de la conception d'une telle classe dans ses plus petits détails, puis de poser un jugement critique.

Si vous avez de la difficulté avec pointeurs et références, voir ../../Sujets/AuSecours/Bref-pointeurs.html et ../../Sujets/Divers--cplusplus/References.html

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

  • Les pointeurs intelligents sont discutés dans POO – Volume 02, pp. 118-133. 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. 94-113 (voir en particulier la page 109 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() {
      return 3; // calcul très savant, évidemment
   }
   void Release() {
      delete this; // urg! Mais propre et légal
   }
};
void relacher(ID3DMachinChouette *p) {
   p->Release();
}
unique_ptr<ID3DMachinChouette, void(*)(ID3DMachinChouette*)> 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() {
      return 3; // calcul très savant, évidemment
   }
   void Release() {
      delete this; // urg! Mais propre et légal
   }
};
struct Relacher { // <-- ICI (toute la classe)
   template <class T>
      void operator()(T p) const {
         p->Release();
      }
};
unique_ptr<ID3DMachinChouette, Relacher> 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).

s/o

Du 19 au 23 septembre

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

S04

28 septembre (AM)

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. 99-118
  • Les templates variadiques  et le Perfect Forwarding sont décrit dans les documents destinés au cours CPA que nous verrons cet hiver, mais bon, la question est venue de la classe alors...

S05

5 octobre (AM)

Au menu :

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

  • Les traits sont décrits dans le document POO – Volume 03, pp. 68-97 puis pp. 98-105

S06

6 octobre (PM)

Au menu :

  • Q04
  • Introduction au clonage :
    • ce que c'est
    • quand copier
    • quand cloner
  • Étude de diverses stratégies envisageables pour prendre en charge la stratégie de croissance au besoin d'un tableau dynamique
  • L'ordre de présentation des approches n'est pas tant de la pire à la meilleure mais bien de la plus susceptible d'être familière à la plus susceptible d'être surprenante (mon évaluation; vous me direz si je me suis trompé)
  • La liste d'approches pour implémenter une stratégie de croissance couvertes en classe inclut :
  • Cette liste n'est pas exhaustive, mais permet de comparer diverses approches (c'est un cours de conception OO, après tout!). Et il existe une solution plus simple, que nous verrons en classe... la semaine prochaine!

N'oubliez pas de remettre le TP00... mais si ça vous coince dans le temps, je le prendrai la semaine prochaine, on ne s'en fait pas avec ça 

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

  • Le clonage est discuté dans le document POO – Volume 01, pp. 204-214

Pour vous divertir :

  • Écrivez le trait nbits_traits<T> tel que nbits_traits<T>::value sera le nombre de bits utilisés pour représenter un T
  • Pour implémenter ce trait, considérez que :
    • sizeof(T) est le nombre de bytes qu'occupe un T en mémoire
    • le nombre de bits dans un byte n'est pas nécessairement huit (même si ce l'est dans l'immense majorité des cas). Techniquement, le nombre de bits dans un byte est décrit par par la constante std::numeric_limits<unsigned char>::digits
    • puisqu'on ne peut instancier un void, faites en sorte que nbits_traits<void>::value soit zéro
  • Écrivez le trait same_size<T,U> tel que same_size<T,T>::value soit vrai et que same_size<T,U>::value soit vrai seulement si nbits_traits<T>::value==nbits_traits<U>::value
  • Écrivez le trait suffix_trait<T> tel que :
    • en général, ce trait ne soit pas défini et corresponde à un type incomplet (donc que le nom soit suivi d'un simple « ; », même pas d'accolades)
    • la méthode de classe suffix_trait<T>::value() retourne une std::string ayant pour valeur un suffixe valide pour T lorsque les littéraux de ce type portent effectivement un suffixe ("l" pour long, "ll" pour long long, "u" pour unsigned int, "ul" pour unsigned long, "ull" pour unsigned long long, "f" pour float et "l" pour long double)
// ...
#include <iostream>
#include <string>
using namespace std;
template <class T>
   void test(ostream &os, const string &nom) {
      os << "Une instance du type " << nom << " occupe " << nbits_traits<T>::value << " bits en memoire" << endl;
      if (same_size<T,int>::value) {
         os << "\t... c'est autant qu'un int!" << endl;
      } else {
         os << "\t... ce n'est pas autant qu'un int" << endl;
      }
   }
int main() {
   test<short>(cout, "short");
   test<float>(cout, "float");
   test<double>(cout, "double");
   test<string>(cout, "string");
   // ceci ne compilerait pas
   // cout << "Le suffixe d'un littéral int est : " << suffix_trait<int>::value() << endl;
   cout << "Le suffixe d'un littéral float est : " << suffix_trait<float>::value() << endl;
   cout << "Le suffixe d'un littéral long double est : " << suffix_trait<long double>::value() << endl;
   cout << "Le suffixe d'un littéral unsigned int est : " << suffix_trait<unsigned int>::value() << endl;
}

Qui sait, peut-être ceci sera-t-il utile en vue d'un prochain minitest?

S07

12 octobre (AM)

Au menu :

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

  • Le truc Barton-Nackman et l'idiome CRTP sont discutés dans des documents qui vous seront livrés cet hiver, parce que votre chic prof est un grand distrait. Heureusement, les liens ci-dessus vous offrent des explications, code à l'appui, alors vous ne serez pas trop démunis

S08

19 octobre (AM)

Au menu :

  • Discussion de la surcharge des opérateurs new, delete, new[] et delete[] de diverses manières et sous divers angles :
  • Q06 et Q07 : les deux questions seront des facettes d'une même activité pratique en lien avec la gestion de la mémoire. L'énoncé du travail est disponible ici : Exercice-Orque-Memoire.html. Le code doit m'être livré par courriel avant le début de la séance S08.

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

  • La gestion avancée de la mémoire est décrite dans le document POO – Volume 03, pp. 136-184

Une infrastructure d'expérimentation est disponible ici si vous souhaitez jouer avec diverses techniques de gestion de la mémoire allouée dynamiquement. Amusez-vous bien!

S09

26 octobre (AM)

Au menu :

  • Poursuite de notre étude des mécanismes de gestion avancée de la mémoire sous divers angles
  • Brève discussion de nouveaux mécanismes avec C++ 17, et d'autres ajouts possibles avec C++ 20.

s/o

Du 2 au 5 novembre

Cette semaine, je suis appelé à donner une conférence à Amsterdam. Profitez-en pour faire avancer votre projet!

s/o

Du 7 au 12 novembre

Cette semaine, je serai à la rencontre de WG21. Vous pourrez me suivre (à travers ../../Sujets/Orthogonal/wg21-2016-Issaquah.html) si vous le souhaitez.

S10

16 novembre (AM)

Au menu :

  • Question Q08
  • Quelques bribes de métaprogrammation pour nous permettre de faire des choix éclairés...
    • par exemple (et ce n'est qu'un exemple), comment une implémentation de std::copy() peut-elle choisir entre utiliser std::memcpy() et faire une boucle réalisant des affectations élément par élément?
    • conséquemment, pourquoi devrions-nous systématiquement utiliser std::copy() (et les fonctions du même acabit) plutôt que de chercher à l'optimiser?
    • et de manière corollaire : comment permettre à std::copy(), à ses cousines et à nos conteneurs d'être aussi efficaces que possible avec nos objets?
    • premier contact avec enable_if
  • Discussion sur les changements de référentiels
  • Premier contact avec les littéraux maison
  • Premier contact avec constexpr

S11

23 novembre (AM)

Au menu :

S12

23 novembre (PM)

La date de remise prévue pour le TP01 était aujourd'hui. Cependant, tel que certains l'ont mentionné, il y a un engorgement de travaux à remettre cette semaine alors remettons cela à la séance S13.

Au menu :

  • Quelques énigmes à résoudre. Vous y réfléchissez, vous proposez votre approche, je vous propose un truc, on discute

Énigme: un collègue vous propose le code suivant :


#ifndef FORME_H
#define FORME_H
//
// Forme.h
//
#include <iosfwd>
class Forme {
public:
   using value_type = char;
private:
   value_type symbole_;
public:
   Forme() : Forme{'*'} {
   }
   Forme(value_type symbole) : symbole_{symbole} {
   }
   value_type symbole() const {
      return symbole_;
   }
   virtual void dessiner(std::ostream &os) const = 0;
   virtual ~Forme() = default;
};
void afficher_formes(Forme *tab, std::size_t n);
#endif

//
// Forme.cpp
//
#include "Forme.h"
#include <iostream>
using namespace std;
ostream& operator<<(ostream &os, const Forme &f) {
   return f.dessiner(os), os;
}
void afficher_formes(Forme *tab, size_t n) {
   for (auto p = tab; p != tab + n; ++p)
      cout << *p << endl;
}

//
// Rectangle.h
//
#ifndef RECTANGLE_H
#define RECTANGLE_H
#include "Forme.h"
template <class T>
   constexpr bool est_entre_inclusif(T candidat, T minVal, T maxVal) {
      return minVal <= candidat && candidat <= maxVal;
   }
class DimensionIncorrecte {};
#include <ostream>
class Rectangle : public Forme {
public:
   using size_type = short;
private:
   static constexpr const size_type MIN_LARGEUR = 1, MAX_LARGEUR = 80;
   static constexpr const size_type MIN_HAUTEUR = 1, MAX_HAUTEUR = 25;
   size_type largeur_,
             hauteur_;
   static constexpr size_type valider_largeur(size_type largeur) {
      return est_entre_inclusif(largeur, MIN_LARGEUR, MAX_LARGEUR)? largeur : throw DimensionIncorrecte{};
   }
   static constexpr size_type valider_hauteur(size_type hauteur) {
      return est_entre_inclusif(hauteur, MIN_HAUTEUR, MAX_HAUTEUR)? hauteur : throw DimensionIncorrecte{};
   }
public:
   Rectangle(size_type largeur, size_type hauteur)
      : largeur_{valider_largeur(largeur)},
        hauteur_{valider_hauteur(hauteur)}
   {
   }
   Rectangle(size_type largeur, size_type hauteur, value_type symbole)
      : Forme{symbole},
        largeur_{valider_largeur(largeur)},
        hauteur_{valider_hauteur(hauteur)}
   {
   }
   size_type largeur() const {
      return largeur_;
   }
   size_type hauteur() const {
      return hauteur_;
   }
   void dessiner(std::ostream &os) const {
      using std::endl;
      for (size_type hau = 0; hau < hauteur(); ++hau) {
         for (size_type lar = 0; lar < largeur(); ++lar)
            os << symbole();
         os << endl;
      }
   }
};
#endif

//
// Principal.cpp
//
#include "Rectangle.h"
#include "Forme.h"
int main() {
   Rectangle tab[] {
      Rectangle{1, 3}, Rectangle{4, 2}, Rectangle{3, 3}
   };
   enum { N = sizeof(tab) / sizeof(tab[0]) };
   afficher_formes(tab, N); // BOUM!
}

Ce collègue vous informe que ce code compile sans avertissement mais, à sa grande surprise, plante sauvagement à l'exécution, apparemment une fois le 1er Rectangle affiché à la console. Quelques questions se posent :

  • Pourquoi ce code est-il syntaxiquement correct mais sémantiquement incorrect?
  • Comment pourrait-on le corriger?
  • À quoi devrait-on faire attention dans le futur pour ne pas revivre de tels ennuis?

Proposition de solution à l'énigme A. Proposition de solution à l'énigme A.

Énigme B : il arrive souvent qu'on ait besoin d'une zone tampon temporaire (un Buffer temporaire, en jargon) pour entreposer des données. Supposons que nous ayons une politique à l'effet que, si nous avons besoin de 4 Ko (4096 bytes) ou moins, nous souhaitions allouer ce tampon sur la pile (classe std::array ou tableau brut, à votre convenance) alors que si nous avons besoin de plus d'espace, nous souhaitions privilégier un vecteur. Nous voulons donc que, dans le code ci-dessous, présumant sizeof(int)==4 et sizeof(double)==8, lbi soit un tableau de 1000 int et que lbd soit un vecteur de 1000 double. Comment y arriver?


#include "local_buffer.h" // <-- votre contribution
#include <algorithm>
int main() {
   using namespace std;
   auto lbi = local_buffer<int,1000>::create(); // lbi --> array<int,1000>
   auto lbd = local_buffer<double, 1000>::create(); // lbd --> vector<double>
   fill(begin(lbi), end(lbi), -1);
   fill(begin(lbd), end(lbd), 3.14159);
}

Proposition de solution à l'énigme B. Proposition de solution à l'énigme B.

Énigme C : vous souhaitez détecter dynamiquement les erreurs de conversion menant à des pertes d'information, comme par exemple dans le programme suivant :


#include <iostream>
#include <limits>
int main() {
   using std::numeric_limits;
   short s = numeric_limits<short>::max();
   int i = s;
   ++i;
   s = i; // <-- ICI
   // ...
}

Proposez une approche permettant, à l'aide d'une syntaxe semblable à celle d'un opérateur de transtypage ISO, de faire en sorte qu'on puisse ne permettre une telle conversion que si elle ne mène pas à une perte d'information ou à une erreur de calcul. Par exemple, en supposant que cette opération se nomme checked_cast, on aurait :


#include <iostream>
#include <limits>
int main() {
   using std::numeric_limits;
   short s = numeric_limits<short>::max();
   int i = s;
   ++i;
   s = checked_cast<short>(i); // <-- ICI (lèverait une exception)
   // ...
}

Petit complément : prenez soin de réfléchir aux cas pour lesquels votre approche serait applicable et aux cas pour lesquels elle ne le serait pas, du moins pas raisonnablement. Êtes-vous en mesure de valider certains de ces a priori dès la compilation du code client?

Proposition de solution à l'énigme C. Proposition de solution à l'énigme C.

Énigme D : soit la classe Tableau<T,N> suivante :


#include <algorithm>
template <class T, std::size_t N>
   class Tableau {
   public:
      using value_type = T;
      using size_type = std::size_t;
      using iterator = value_type*;
      using const_iterator = const value_type*;
   private:
      value_type elems[N];
   public:
      constexpr size_type size() const {
         return N;
      }
      iterator begin() noexcept {
         return std::begin(elems);
      }
      const_iterator begin() const noexcept {
         return std::begin(elems);
      }
      iterator end() noexcept {
         return std::end(elems);
      }
      const_iterator end() const noexcept {
         return std::end(elems);
      }
      constexpr Tableau() : elems{ {} } {
      }
      template <class U>
         Tableau(const Tableau<U,N> &autre) {
            using std::copy;
            copy(autre.begin(), autre.end(), begin());
         }
      //
      // Sainte-Trinité implicitement Ok
      //
      value_type& operator[](size_type n) {
         return elems[n];
      }
      value_type operator[](size_type n) const {
         return elems[n];
      }
      bool operator==(const Tableau &autre) const {
         using std::equals;
         return equals(begin(), end(), autre.begin());
      }
      bool operator!=(const Tableau &autre) const {
         return !(*this == autre);
      }
   };

On souhaite savoir quelle est la meilleure manière d'implémenter les opérateurs == et != dans les cas suivants :

  • Si on compare un Tableau<T,N> avec un Tableau<T,M> pour M != N
  • Si on compare un Tableau<T,N> avec un Tableau<U,N> pour deux types T et U distincts, et
  • Si on compare un Tableau<T,N> avec un Tableau<U,M> pour M != N et deux types T et U distincts

Proposition de solution à l'énigme D. Proposition de solution à l'énigme D.

Énigme E : votre entreprise ne souhaite pas utiliser de templates parce ses leaders techniques craignent la croissance de la taille des binaires. Leur justification est que, selon eux, la génération d'un type par cas d'utilisation mène à... trop de types, et en particulier à trop de méthodes. Cela dit, ils n'ont pas tort (bien qu'en pratique, sans templates, il aurait probablement fallu faire le même travail, mais manuellement).

Vous avez entendu parler « entre les branches » qu'il est possible, dans certains cas (plus spécifiquement, dans le cas des pointeurs) de réduire la quantité de code générée avec des templates en comparaison avec ce qui serait requis pour une implémentation équivalente programmée manuellement. L'énigme ici est : comment écrire une classe représentant une pile d'éléments d'un type T donné telle que, quand cette classe est instanciée pour des types T qui sont en fait des pointeurs (p. ex. : pile<int*>, pile<string*>, pile<Objet3D*>), le compilateur génère en pratique moins que code que si nous codions chaque fois une classe à part entière.

Proposition de solution à l'énigme E. Proposition de solution à l'énigme E.

Énigme F : soit la classe générique wow_genial<T> pour laquelle vous avez une implémentation à proposer pour un nombre fini mais relativement grand de types avec une implémentation semblable dans chaque cas, et qui ne doit pas compiler si on l'applique à d'autres types que ceux-là. Pour les fins de l'exercice, supposez que wow_genial<T> doive fonctionner pour les types char, signed char, void*, std::wstring et Objet3D*.  Une approche simple serait d'écrire :


template <class>
   class wow_genial;
template <>
   class wow_genial<char> {
      // ...
   };
template <>
   class wow_genial<signed char> {
      // ...
   };
template <>
   class wow_genial<void*> {
      // ...
   };
template <>
   class wow_genial<std::wstring> {
      // ...
   };
template <>
   class wow_genial<Objet3D*> {
      // ...
   };

... mais cela mène à beaucoup de redondance de code étant donné que chacune des implémentations est essentiellement identique aux autres (au type près). On cherche à identifier une solution plus générale.

Il y a au moins deux approches intéressantes pour résoudre ce problème. Pouvez-vous en suggérer une?

Proposition de solution à l'énigme F. Proposition de solution à l'énigme F.

Pour des pistes supplémentaires, vous pouvez jeter un coup d'oeil à cet exemple, qui utilise un static_for_each et un static_accumulate pour diverses tâches de calcul et d'affichage.

S13

30 novembre (AM)

Au menu : cours dont vous êtes, par vos questions, les artisans. Si vous n'avez pas de questions particulières, je ferai :

N'oubliez pas de remettre le TP01

S14

7 décembre (AM)

Chic examen plein d'amour

« S15 »

 

Semaine de finalisation et de présentation du projet

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 A2016 suivent. Notez que l'écart-type n'est pas significatif étant donné la pondération des questions (sur cinq points, un point de différence représente , ce qui bousille quelque peu cette composante statistique).

 Question   Séance 
Q00 S02
Q01 S03
Q02 S04
Q03 S05
Q04 S06
Q05 S07
Q06 S08
Q07 S08
Q08 S10
Q09 S13
 :
(huit 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

Ce travail s'intègre aux travaux pratiques communs de la session, chapeautés par les cours INF711 (Fondements d'infographie appliquée) et INF781 (Intelligence artificielle appliquée). Les équipes de travail seront les mêmes dans les deux cours.

Si vous éprouvez des difficultés à intégrer un élément technique précis, je suis ouvert à ce que vous proposiez une thématique technique équivalente.

Dans le doute sur le caractère équivalent ou non de votre proposition, contactez-moi!

Réfléchissez à l'application pertinente et justifiée dans ce travail :

Notez que ce que vous proposerez doit se distinguer des exemples proposés par votre chic prof. Présentez votre application en la mettant en contexte et en décrivant son fonctionnement, code à l'appui.

Notez qu'il arrive régulièrement que des étudiant(e)s de ce cours arrivent au point où il leur faut livrer ce travail pratique et font le constat qu'ils n'ont pas encore commencé à appliquer des techniques et des concepts du cours, ayant (consciemment ou non) fait le « choix » de rester dans le confort de leurs habitudes. Ne vous faites pas prendre et prévoyez le coup en mettant le plus systématiquement possible en pratique ce que vous voyez dans le cours!

Vous devrez joindre à votre document un diagramme UML des principales classes auxquelles votre équipe pense avoir recours et des relations entre celles-ci. Le niveau de détail vous appartient mais doit être suffisant pour vous assister dans votre travail. Si un seul diagramme vous semble difficile à gérer ou à lire, rien ne vous empêche de produire quelques diagrammes plus petits dans la mesure où le tout reste cohérent – en pratique, les entreprises tendent à faire un gros diagramme qu'elles exposent sur un mur de manière à ce que ce diagramme soit aisément accessible au besoin. Rien ne vous empêche d'en faire autant.

Le diagramme UML est une tâche importante et beaucoup plus complexe qu'il n'y paraît puisque réaliser ce diagramme vous forcera à réfléchir aux relations des diverses classes principales du système que vous allez développer. Pour reprendre une question posée en classe: les lois de la physique sont-elles des modules intégrés aux objets qui leur sont assujettis? Résident-elles dans un ou plusieurs gestionnaires externes agissant sur les objets? Quel est le niveau de couplage souhaité? Les relations entre lois et entités soumises aux lois seront-elles génériques ou polymorphiques?

Vous devrez remettre un document par équipe montrant clairement les éléments que vous intégrerez (et qui s'en portera garant) et mettant en valeur la pertinence de vos choix, quitte à y mettre du code si vous en êtes capables. Le but de ce travail est de vous aider à progresser dans votre développement, pas de vous nuire. Des changements ultérieurs à votre design seront possibles s'ils sont justifiés.

Vous pourrez me remettre ce 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.

Consignes du TP01

Il se fait tard, vous souhaitez produire et conclure votre session, et je souhaite pouvoir savoir où vous en êtes sans toutefois vous surcharger. Voici ce que je vous propose :

L'idée est de produire une autocritique individuelle et de design global qui puisse servir de rampe de lancement au projet de cet hiver. Autocritique ne signifie pas autoflagellation : personne n'est parfait et l'école est un lieu d'apprentissage. Je lirai ce document pour voir la qualité de votre analyse et de vos comparatifs entre solutions adoptées et alternatives envisageables/ envisagées (si une alternative de meilleure qualité me semble évidente, je vous l'indiquerai au passage).

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.


Valid XHTML 1.0 Transitional

CSS Valide !