Quelques raccourcis :

420-KHJ-LG – Intégration de techniques nouvelles en informatique industrielle

Ceci est un petit site de support pour le cours 420-KHJ-LG – Intégration de techniques nouvelles en informatique industrielle.

Vous trouverez aussi des liens sur divers langages (dont C++, notre outil de prédilection dans ce cours) un peu partout dans http://h-deb.clg.qc.ca/. Portez une attention particulière à ../../../Sujets/Divers--cplusplus/index.html.

Les diverses sections de cette page (en fonction desquelles vous trouverez quelques liens dans l'encadré à droite) vous mèneront elles-aussi sur des pistes qui vous permettront d'explorer un peu plus par vous-mêmes, de valider vos acquis et d'enrichir votre apprentissage.

Cliquez sur cette cible pour le plan de cours, sous forme électronique

Exercices de révision : cliquez sur cette cible pour quelques exercices de révision

À propos des demandes spéciales

Si vous avez envie que l'on traite de questions spécifiquement intéressantes à vos yeux, je suis bien ouvert à couvrir ces thématiques (dans la mesure où vous m'informez de votre intérêt, évidemment).

Quelques demandes reçues jusqu'ici :

À propos des notes de cours

Je vous ai indiqué par Colnet que je ne ferais imprimer que le nombre de documents de notes de cours requis cette session, pour des raisons environnementales. Voici la situation pour le moment :

Oui, je le veux Non, je n'en veux pas

Dominic Bélanger

Alexandre L'Africain

Maxime Boisvert

 

Samuel Godard

 

Julien Labrie-Forest

 

Derek Masson

 

Jean-Mathieu Rousseau

 

Je devrais aller démarrer le processus de reproduction la semaine prochaine (le plus tôt possible étant donné mon horaire de fou). À suivre...

Pratiques de correction

Je corrige les programmes en appliquant des codes de correction. Vous trouverez ici la liste des codes les plus fréquents

Ma stratégie de correction en tant que telle (pour le code, à tout le moins) est résumée ici

Détail des séances en classe

Date Séance Détails

22 août

T00

Au menu :

  • Présentation du cours et du plan de cours
  • Petits exercices de révision (solutions)
  • Discussions en classe en lien avec les réponses proposées par les étudiant(e)s et celles suggérées par l'enseignant

Notez bien les identifiants pour les quatre documents que vous devrez vous procurer à la coop :

  • POO – Volume 00, v. 1,96 (387 pages) → 1A16-200
  • POO –Volume 01, v. 1,91 (263 pages) → 1A16-201
  • POO –Volume 02, v. 1,99 (287 pages) → 1A16-202
  • POO –Volume 03, v. 1,998 (301 pages) → 1A16-203

26 août

L00

Petit défi de programmation pour vous :

Par équipe de deux (ou trois, si tous s'impliquent) personnes, définissez une classe tite_chaine représentant une petite chaîne de caractères (susceptible d'être utilisée dans un système embarqué ou dans un jeu vidéo).

Vos contraintes sont les suivantes :

  • Essentiel : sizeof(tite_chaine)==256
  • Essentiel : aucune allocation dynamique de mémoire ne doit être faite dans les méthodes d'une tite_chaine
  • Essentiel : si s est une tite_chaine, alors s.size() retourne sa taille, exprimée en nombre de caractères, est en temps constant (complexité )
  • Invariant : la taille d'une tite_chaine, en nombre de caractères, doit être entre 0 et 255 inclusivement
  • Essentiel : la Sainte-Trinité doit être supportée par votre classe
  • Essentiel : les opérateurs suivants doivent être offerts : opérateurs relationnels (==, !=, <, <=, > et >=), opérateur [] en version const et non-const
  • Essentiel : offrir une méthode empty() retournant true seulement si la tite_chaine est vide et qui s'exécute en temps constant (complexité )
  • Invariant : une tite_chaine est vide si sa taille est zéro
  • Essentiel : un constructeur par défaut de tite_chaine doit construire une chaîne vide
  • Essentiel : offrir un constructeur prenant en paramètre un const char* (chaîne ASCIIZ)
  • Essentiel : offrir un constructeur prenant en paramètre un const std::string&

Quelques questions auxquelles je vous suggère de réfléchir :

  • Doit-on implémenter la Sainte-Trinité pour cette classe? Expliquez votre réponse
  • Doit-on implémenter le mouvement pour cette classe? Expliquez votre réponse
  • Comment gérerez-vous les constructeurs recevant un paramètre susceptible de violer vos invariants (p. ex. : une chaîne de plus de 255 caractères)?
  • Discussion des choix de design
  • Élaborer un code de test correct pour tite_chaine

Prenez soin de justifier par écrit vos choix d'implémentation. Prenez aussi soin de documenter les préconditions de vos fonctions, leurs postconditions, leur complexité algorithmique, de même que  et invariants de votre classe. Enfin, implémentez votre design et testez-le avec rigueur.

29 août

T01

Au menu :

  • Retour sur le problème de la tite_chaine
  • Voici un petit défi de programmation pour vous :

Seul ou par équipe de deux personnes, définissez une classe Mutex capable de représenter, pour une plateforme donnée, un mutex « système » ou un mutex local au processus, selon un choix fait à la construction.

Plus précisément, avec Microsoft Windows, votre classe encapsulera un CRITICAL_SECTION ou un HANDLE sur un mutex, et offrira les services « normaux » pour une telle classe (construire, détruire. obtenir, relâcher).

Votre classe se voudra sécuritaire à utiliser. Conséquemment, vous chercherez à réduire les risques d'erreurs dans le code client (mutex non libérés, fuites de ressources, et autres irritants découlant d'un usage incorrect de votre classe).

Souvenez-vous que la maxime de Scott Meyers : « on ne peut empêcher un individu qui insiste pour faire des bêtises de se placer dans une situation déplaisante, mais on peut lui fournir des outils de qualité qui réduiront ce risque » (traduction libre). C'est ce que nous visons ici : une classe avec laquelle il est plus simple de bien travailler que de mal travailler.

Les méthodes qui peuvent être const le seront. Vous viserez plus particulièrement  à ce que les méthodes obtenir() et relacher() soient const.

Le code client ne doit pas être couplé de quelque manière que ce soit à la plateforme (vous ne devez rien laisser filtrer pour lui du système d'exploitation sous-jacent).

Quelques questions auxquelles je vous suggère de réfléchir :

  • Doit-on implémenter la Sainte-Trinité pour cette classe? Expliquez votre réponse
  • Doit-on implémenter le mouvement pour cette classe? Expliquez votre réponse
  • Quel est l'espace requis en mémoire pour votre implémentation? Pourrait-elle être plus compacte? Cela serait-il avantageux?

2 septembre

L01

Au menu :

  • Suite et fin du travail entamé à la séance T01
  • À la fin de la séance, vous devez me remettre, en format  imprimé, votre classe Mutex et un exemple de code client utilisant cette classe
  • Vous partirez en « vacances » avec quelques exercices de base à faire (exercices, document de support – pour vous éviter de jouer au perroquet)

5 septembre

s/o

Fête du travail

9 septembre

L02

Au menu :

  • Retour sur le travail impliquant deux implémentations pour une même classe Mutex, donné à la séance T01
  • Travail sur les exercices de base, à remettre aujourd'hui (je ramasserai l'exercice À EX04 en format imprimé). Je m'attends à ce que :
    • le code n'ait plus de fuites de ressources (attention : ce n'est pas parce qu'une fonction n'est pas appelée dans le main() de test que je vous ai donné en accompagnement qu'elle a le droit de fuir!)
    • le code ne plante pas (ça semble évident, mais tout de même)
    • les irritants suivants soient réglés (ne faites cette partie qu'une fois les fuites et les plantages réglés, sinon vous allez vous empêtrer inutilement dans la complexité) :
      • remarquez que la méthode permettant de calculer le nombre d'éléments dans une ListeEntiers doit présentement compter les éléments. Ainsi, plus la liste contiendra d'éléments et plus l'exécution de cette fonction sera longue (algorithme de complexité est la longueur de la liste). C'est très agaçant, et il serait préférable d'avoir un algorithme en temps constant (complexité )
      • remarquez que la méthode permettant d'ajouter un élément à une ListeEntiers doit parcourir cette liste pour en trouver le point d'insertion. Ainsi, plus la liste contiendra d'éléments et plus l'exécution de cette fonction sera longue (algorithme de complexité est la longueur de la liste). C'est très agaçant, et il serait préférable d'avoir un algorithme en temps constant (complexité )
      • évidemment, si vous constatez d'autres irritants, corrigez-les!
    • une fois ces changements apportés, assurez-vous que les autres méthodes fonctionnent encore (un programme de test plus élaboré vous sera utile pour cette fin). En particulier, la méthode qui inverse l'ordre des éléments d'une liste bénéficiera probablement d'un peu d'amour de votre part

12 septembre

T02

Au menu :

  • Discussion des solutions proposées à l'exercice EX04 du document exercices de base
  • Effort de généricité et de rigueur. En vue de T04 (donc vous serez occupé(e)s pour deux semaines, en plus de ce que vous aurez à faire en mon absence, mais au moins ce sera amusant  ), vous devrez implémenter les algorithmes suivants, de telle sorte qu'ils soient utilisables dans le respect des contraintes énoncées dans chaque cas. Chacun de ces algorithmes existe dans la bibliothèque standard sous un autre nom, mais vous n'avez pas le droit d'utiliser l'algorithme standard correspondant pour résoudre le problème proposé (vous pouvez évidemment utiliser std::swap() ou d'autres algorithmes auxiliaires, mais je ne veux pas que votre implémentation de est_trie() appelle is_sorted(), à titre d'exemple). Ça peut sembler beaucoup, mais chacun est relativement petit, et l'exercice est extrêmement pertinent à faire (de plus, je suspecte que vous allez vous amuser!) :
    • à titre de révision, implémentez inverser(debut,fin) représente une séquence à demi-ouverte, de telle sorte que cet algorithme inverse l'ordre des éléments de cette séquence (p. ex. : deviendrait ). Il faut que cet algorithme soit applicable à un tableau, un vector ou une list. Testez votre implémentation en vous assurant qu'elle fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre impair d'éléments, et aucun élément (cas où debut==fin)
    • implémentez rotater_gauche(debut,fin) représente une séquence à demi-ouverte, de telle sorte que cet algorithme permute vers la gauche l'ordre des éléments de cette séquence (p. ex. : deviendrait ). Il faut que cet algorithme soit applicable à un tableau, un vector ou une list. Testez votre implémentation en vous assurant qu'elle fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre impair d'éléments, et aucun élément (cas où debut==fin)
    • implémentez rotater_droite(debut,fin) représente une séquence à demi-ouverte, de telle sorte que cet algorithme permute vers la droite l'ordre des éléments de cette séquence (p. ex. : deviendrait ). Il faut que cet algorithme soit applicable à un tableau, un vector ou une list. Testez votre implémentation en vous assurant qu'elle fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre impair d'éléments, et aucun élément (cas où debut==fin)
    • à titre de révision, implémentez compter(debut,fin,val) représente une séquence à demi-ouverte, de telle sorte que cet algorithme retourne le nombre d'éléments e tels que e==val s'avère (p. ex. : pour , compter(begin(vals),end(vals),3)==1). Il faut que cet algorithme soit applicable à un tableau,  un vector une list ou une forward_list. Testez votre implémentation en vous assurant qu'elle fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre impair d'éléments, et aucun élément (cas où debut==fin)
    • à titre de révision, implémentez compter_si(debut,fin,pred) représente une séquence à demi-ouverte, de telle sorte que cet algorithme retourne le nombre d'éléments e tels que pred(e) s'avère (p. ex. : pour , compter_si(begin(vals),end(vals),[](int n){return n%2!=0;})==4). Il faut que cet algorithme soit applicable à un tableau,  un vector une list ou une forward_list. Testez votre implémentation en vous assurant qu'elle fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre impair d'éléments, et aucun élément (cas où debut==fin)
    • implémentez trouver(debut,fin,val) représente une séquence à demi-ouverte, de telle sorte que cet algorithme retourne un itérateur sur l'élément e tel que e==val s'avère. Il faut que cet algorithme soit applicable à un tableau,  un vector une list ou une forward_list. Testez votre implémentation en vous assurant qu'elle fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre impair d'éléments, et aucun élément (cas où debut==fin)
    • implémentez trouver_si(debut,fin,pred) représente une séquence à demi-ouverte, de telle sorte que cet algorithme retourne un itérateur sur l'élément e tel que pred(e) s'avère. Il faut que cet algorithme soit applicable à un tableau,  un vector une list ou une forward_list. Testez votre implémentation en vous assurant qu'elle fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre impair d'éléments, et aucun élément (cas où debut==fin)
    • implémentez est_trie(debut,fin) représente une séquence à demi-ouverte, de telle sorte que cet algorithme retourne true seulement si la séquence est triée. Il faut que cet algorithme soit applicable à un tableau,  un vector une list ou une forward_list. Testez votre implémentation en vous assurant qu'elle fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre impair d'éléments, et aucun élément (cas où debut==fin)
    • implémentez est_trie(debut,fin,pred) représente une séquence à demi-ouverte, de telle sorte que cet algorithme retourne true seulement si la séquence est triée en fonction du prédicat pred. Il faut que cet algorithme soit applicable à un tableau,  un vector une list ou une forward_list. Testez votre implémentation en vous assurant qu'elle fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre impair d'éléments, et aucun élément (cas où debut==fin)
    • implémentez supprimer_duplicats(debut,fin) représente une séquence à demi-ouverte triée, de telle sorte que cet algorithme supprime les duplicats de cette séquence. Il faut que cet algorithme soit applicable à un tableau,  un vector ou une list. Testez votre implémentation en vous assurant qu'elle fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre impair d'éléments, et aucun élément (cas où debut==fin). Cette fonction ne peut évidemment pas vraiment éliminer les éléments de la séquence (elle ne connaît pas le conteneur et doit fonctionner même avec un tableau!) alors pour « éliminer » un élément, elle doit le placer à la fin de la séquence. Sa valeur de retour est la « nouvelle fin » de la séquence (un itérateur sur le premier des éléments « éliminés »)
  • Il serait facile de tricher en réalisant cet exercice, mais je vous invite fortement à ne pas le faire, et à tirer profit de ce qu'ils peuvent vous enseigner
  • Je vous indiquerai, peu de temps avant T04, les deux algorithmes dont je vous demanderai de remettre l'implémentation.

16 septembre

L03

Au menu :

  • Réponse à la question suivante :
    • Derek Masson souhaite savoir si for(int i = 0; i < x; ++i) est plus rapide que for(int i = 0; i < x; i++), et si oui, quelle est la raison pour cet état de fait
    • nous avons dévié sur un (bref|) survol d'OpenMP, de TBB et de PPL
  • Travail sur les exercices présentés à T04

19 septembre

T03

Au menu : je serai remplacé par mon estimé collègue et ami Rachid Kadouche, qui vous proposera ceci :

« Voilà le menu :

En entrée, je vais parler de dll en général : avantages et inconvénients, le rôle des dll dans le développement d’applications.

Puis pour la salade, on verra comment écrire une dll en C++ et C#, comment utiliser une dll dans un programme.

Au plat de résistance, il y aura du piquant doux, notamment comment appeler une dll écrite dans un autre langage, comme exemple nous verrons comment ça se passe entre les deux langages C++ et C#. Plusieurs exercices sont prévus au dessert pour bien digérer tout le menu. »

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

N'oubliez pas qu'aujourd'hui, c'est le « Talk Like A Pirate Day ». C'est Sa Sainteté qui serait fière de savoir que vous participez à cet important événement!

23 septembre

L04

Au menu : je serai remplacé par mon estimé collègue et ami Rachid Kadouche, qui vous proposera un laboratoire en lien avec la matière examinée lors de la séance T03

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

26 septembre

T04

Au menu :

  • Remise de votre version des algorithmes suivants :
    • la fonction est_trie(debut,fin,pred)
    • la fonction rotater_droite(debut,fin)
  • (puisque je ne vous ai pas avertis d'avance, je les accepterai au plus tard au début de L05 au début de T05, le délai étant dû à mon absence lors de L05)
  • Pour le reste de la séance, et pour la prochaine, un petit exercice (à remettre à la fin de T05). Soit le code suivant :
#include <ostream>
#include <istream>
#include <string>
#include <algorithm>
#include <memory>
#include <vector>

using namespace std;

template <class T, class U>
   constexpr bool est_entre_demi_ouvert(const T &val, const U &seuilmin, const U &seuilmax) {
      return seuilmin <= val && val < seuilmax;
   }
template <class T, class U>
   bool est_entre_inclusif(const T &val, const U &seuilmin, const U &seuilmax) {
      return seuilmin <= val && val <= seuilmax;
   }

class ContrainteNonRespectee { };

template <class T, class Pred>
   const T& valider_contrainte(Pred pred, const T &val) {
      return !pred(val)? throw ContrainteNonRespectee{} : val;
   }

class Monstre {
   string nom_;
public:
   Monstre(const string &nom) : nom_{ nom } {
   }
   string nom() const {
      return nom_;
   }
   virtual ostream& hurler(ostream&) const = 0;
   virtual void mettre_a_jour(ostream&, istream&) = 0;
   virtual ~Monstre() = default;
   //
   // VOTRE CODE ICI
   //
};

ostream& operator<<(ostream &os, const Monstre &monstre) {
   return monstre.hurler(os);
}

bool operator==(const Monstre &a, const Monstre &b) {
   return a.nom() == b.nom();
}
bool operator!=(const Monstre &a, const Monstre &b) {
   return !(a == b);
}

class Bestiole : public Monstre {
   double agacement_; // [0..1)
public:
   Bestiole(const string &nom, double agacement)
      : Monstre{ nom },
        agacement_{
           valider_contrainte([](const double &val) {
              return est_entre_demi_ouvert(val, 0.0, 1.0);
           }, agacement)
        }
   {
   }
   friend void RendreMoinsAchalant(Bestiole&);
   friend void RendrePlusAchalant(Bestiole&);
   double agacement() const {
      return agacement_;
   }
   ostream& hurler(ostream &os) const override {
      return os << "Je suis " << nom() << " la bestiole, achalant a " << agacement() * 100 << "%";
   }
   void mettre_a_jour(ostream &out, istream &in) override {
      out << R"(Vos options :
   1) rendre plus achalant
   2) rendre moins achalant
Votre choix: )";
      int choix;
      while(in >> choix && !est_entre_inclusif(choix, 1, 2))
         out << R"(Vos options :
   1) rendre plus achalant
   2) rendre moins achalant
Votre choix: )";
      switch (choix) {
      case 1:
         RendrePlusAchalant(*this);
         break;
      case 2:
         RendreMoinsAchalant(*this);
         break;
      }
   }
   //
   // VOTRE CODE ICI
   //
};

class Bibitte : public Monstre {
   int puanteur_; // [1 .. 100]
   double mechancete_; // [0..1)
public:
   Bibitte(const string &nom, int puanteur, double mechancete)
      : Monstre{ nom },
        puanteur_{
           valider_contrainte([](const int &val) {
              return est_entre_inclusif(val, 1, 100);
           }, puanteur)
        },
        mechancete_{
           valider_contrainte([](const double &val) {
              return est_entre_demi_ouvert(val, 0.0, 1.0);
           }, mechancete)
        }
   {
   }
   friend void Laver(Bibitte&);
   friend void Salir(Bibitte&);
   int puanteur() const {
      return puanteur_;
   }
   double mechancete() const {
      return mechancete_;
   }
   ostream& hurler(ostream &os) const override {
      return os << "Je suis " << nom() << " la bibitte; puant a " << puanteur() << "% et mechant a " << mechancete() * 100 << "%";
   }
   void mettre_a_jour(ostream &out, istream &in) override {
      out << R"(Vos options :
   1) laver
   2) salir
Votre choix: )";
      int choix;
      while(in >> choix && !est_entre_inclusif(choix, 1, 2))
         out << R"(Vos options :
   1) laver
   2) salir
Votre choix: )";
      switch (choix) {
      case 1:
         Laver(*this);
         break;
      case 2:
         Salir(*this);
         break;
      }
   }
   //
   // VOTRE CODE ICI
   //
};

void Laver(Bibitte &b) {
   b.puanteur_ = 1;
}
void Salir(Bibitte &b) {
   b.puanteur_ = min(100, b.puanteur_ + 5);
}
void RendreMoinsAchalant(Bestiole &b) {
   b.agacement_ -= b.agacement_ * 0.5;
}
void RendrePlusAchalant(Bestiole &b) {
   b.agacement_ += (1.0 - b.agacement_) * 0.5;
}


template <class T>
   unique_ptr<T> modifier_potentiellement(unique_ptr<T> p, ostream &out, istream &in) {
      //
      // VOTRE CODE ICI
      //
   }

#include <iostream>

int main() {
   vector<unique_ptr<Monstre>> v;
   v.push_back(make_unique<Bibitte>("Joe", 30, 0.75));
   v.push_back(make_unique<Bestiole>("Fred", 0.23));
   v.push_back(make_unique<Bestiole>("Bill", 0.8));
   v.push_back(make_unique<Bibitte>("Zebda", 66, 0.11));
   v.push_back(make_unique<Bestiole>("Guy", 0.99));
   for (auto & p : v) {
      cout << "Avant: " << *p << '\n';
      p = modifier_potentiellement(std::move(p), cout, cin);
      cout << "Apres: " << *p << '\n';
   }
}

Vous aurez remarqué que quelques sections de code manquent, sans doute effacées par une créature immonde ou féroce. Votre mandat est de compléter ce programme pour qu'il redevienne fonctionnel.

Que doit faire ce programme, me direz-vous? Simple :

  • La fonction modifier_potentiellement() doit offrir à l'usager de mettre à jour le Monstre reçu en paramètre. La méthode polymorphique mettre_a_jour() d'un Monstre vous aidera en ce sens
  • Une fois la mise à jour réalisée, modifier_potentiellement() devra demander à l'usager s'il est satisfait des changements. Si l'usager répond par l'affirmative, alors la mise à jour sera conservée, sinon elle ne le sera pas
  • Une manière simple d'implémenter ceci est de faire un duplicat de l'objet au début de modifier_potentiellement(), de modifier l'une des deux versions de l'objet en gardant l'autre intacte, et de retourner l'objet modifié ou celui qui ne l'est pas, selon le cas. Cependant, vous avez le droit d'être créatifs dans votre démarche, dans la mesure où c'est propre, où ça fonctionne et où ça ne provoque pas de fuites de ressources.

N'hésitez pas à poser des questions. Cet exercice est à faire sur une base individuelle.

30 septembre

L05

Prof absent pour cause de chibouki fiévreux à la maison. Profitez-en pour travailler sur votre projet de fin d'études!

3 octobre

T05

Au menu :

  • Classes polymorphiques et classes concrètes (Java, C++, C#)
  • Retour sur le clonage (avec C++, avec C#)
  • Classes terminales (avec C++, avec Java, avec C#) : pourquoi et comment
  • Quand copier, quand cloner
  • Comment copier, comment cloner

Pour celles et ceux qui ont envie d'un survol des pointeurs intelligents comme unique_ptr, voir ../../../Sujets/AuSecours/pourquoi_pointeurs_intelligents.html

Poursuite du travail entamé à T04

7 octobre

L06

Au menu :

10 octobre

s/o

Action de grâce

11 octobre

T06

Au menu :

Attention : mardi avec horaire du lundi!

14 octobre

L07

Au menu :

Dominic Bélanger demande « On n'a pas vu comment appeler une .DLL de C# en C++, la démarche est-elle similaire? Devrais-je faire une .DLL en C++ Managed pour plus d'efficacité, ou est-ce que ça ne changera pas grand chose à part compliquer le code? »

Rappel des étapes côté C# :

  • Utiliser System.Runtime.InteropServices
  • Conformer l'interface à exposer au standard COM, et plus précisément à l'interface IUnknown
  • Apposer un GUID au serveur qui implémente cette interface
  • Exposer l'assemblage à COM (mettre COM Visible à true dans AssemblyInfo.cs)
  • Signer l'assemblage (la .DLL), que ce soit à la ligne de commande, en utilisant sn -k, ou dans les options du projet
  • Déployer l'assemblage dans la GAC (Global Assembly Cache) à la ligne de commande avec gacutil -i
  • Extraire une bibliothèque de types de l'assemblage à la ligne de commande avec tlbexp (plus précisément : tlbexp ze_dll.dll /out:ze_dll.tlb)
  • Inscrire la correspondance entre la bibliothèque de types et l'assemblage dans la bae de registres, la ligne de commande avec regasm (plus précisément : regasm ze_dll.dll /tlb:ze_dll.tlb /codebase)

Rappel des étapes côté C++ :

  • Importer la bibliothèque de types (fichier .TLB) extraite de l'assemblage .NET à l'aide de la directive non-portable #import, par exemple #import "ze_dll.tlb" named_guids
  • Utiliser les pointeurs intelligents ainsi générés
  • Bingo!

J'avais dit que je vous préparerais un travail pratique pour aujourd'hui, mais étant donné la demande spéciale à laquelle nous avons répondu, j'ai préféré repousser ce travail pratique à la séance T07.

17 octobre

T07

Au menu :

  • Présentation du travail à faire pour le début de la séance L09 :
    • vous devrez livrer un fournisseur de labyrinthe sous la forme d'une DLL
    • ce fournisseur de labyrinthe n'aura à supporter que des clients écrits en C++, pour simplifier le travail
    • il devra être possible de faire les choses suivantes :
      • obtenir une carte (initialement)
      • obtenir un contrôleur de jeu pré-mouvement (à chaque mouvement)
      • obtenir un contrôleur de jeu post-mouvement (à chaque mouvement)
      • obtenir une liste d'effets spéciaux (initialement)
      • afficher cette carte
      • déplacer un avatar dans la carte à l'aide de touches du clavier (une case à la fois)
      • changer l'implémentation du fournisseur de labyrinthe sans recompiler le client (preuve à l'appui). Vous ne devez pas supporter le changement d'implémentation pendant que la partie s'exécute, seulement entre deux parties
    • la carte sera rectangulaire et devra être représentée par une chaîne de caractères où chaque caractère représente en soi une case, de même qu'une largeur et une hauteur
    • chaque contrôleur de jeu sera représenté par une fonction / un foncteur / une lambda. Le contrôleur de jeu pré-mouvement sera appelé avant chaque mouvement. Le contrôleur de jeu post-mouvement sera appelé après chaque mouvement.
    • le contrôleur de jeu pré-mouvement sera un prédicat servant à autoriser un mouvement. Il prendra en paramètre la position avant le mouvement et la position après le mouvement, et retournera true seulement si le mouvement est autorisé
    • le contrôleur de jeu post-mouvement prendra en paramètre la position où se trouve désormais l'avatar de même que le symbole sur cette case, et qui retournera une paire constituée (a) d'un code représentant succes (la partie est gagnée), echec (la partie est perdue, ou il y a eu tricherie – passer à traver un mur, se téléporter, sortir des limites de la carte) et en_cours (la partie se poursuit), et (b) d'une chaîne de caractères descriptive de la situation, que le client pourra afficher pour informer le joueur de l'état de la partie
      • l'idée ici est que la DLL contrôlera les règles du jeu. Par exemple, elle peut donner à l'avatar une durée de vie limitée dans le temps, ou un nombre maximal de pas à franchir avant de mourir de faim
    • une case pourra initialement être vide, un mur, une sortie, ou encore la case où débute l'avatar
    • certaines cases devront être munies d'effets spéciaux, s'activant lorsque l'avatar passe sur elles
      • l'idée ici est de permettre d'enrichir le jeu dynamiquement. Par exemple, si l'avatar ne peut franchir qu'un certain nombre de pas avant de mourir de faim, un effet spécial pourrait être des victuailles qui lui redonneront un peu de force. Une autre option serait de permettre à l'avatar de passer à travers les murs pour une courte période de temps
    • le code client ne connaît pas d'avance les effets spéciaux possibles. Une fois la carte chargée, le client devra parcourir chaque case qui n'est pas un mur et lui attribuer de manière pseudoaléatoire un effet spécial avec une probabilité uniforme de
    •  si une case doit avoir un effet spécial, alors le code client en choisira un dans la liste reçue du fournisseur de labyrinthe. Un effet spécial sera une fonction retournant un caractère; ce caractère remplacera celui normalement placé sur la carte à l'endroit choisi
  • Ce travail comptera pour deux, et peut être fait en équipe de deux personnes

Je vous invite à faire quelque chose de propre, car il n'est pas exclu que ce travail ait une suite...

21 octobre

L08

Au menu :

  • Poursuite du travail entrepris à la séance T07

24 octobre

T08

Cours annulé pour cause de prof ayant passé la nuit à l'hôpital avec son plus jeune, et devant rester de garde à la maison avec lui. Conséquemment, la remise du travail prévue à la séance L09 sera déplacée au début de la séance T09.

28 octobre

L09

Au menu :

  • Démonstration d'une solution possible au travail entrepris à la séance T07
  • Quelques questions, par exemple « pourquoi une fonction extern "C" peut-elle retourner un vector<int>* mais pas un vector<int>? »
  • Poursuite du travail entrepris à la séance T07

31 octobre

T09

Au menu :

  • Au début de la séance, remettre le travail entrepris à la séance T07
  • Utiliser des sockets de type flux avec C++ (survol)
  • Quelques techniques de sérialisation

Joyeuse Halloween!

4 novembre

s/o

Journée de mise à niveau (cours suspendus)

7 novembre

T10

Au menu : je serai remplacé par (à déterminer) qui vous proposera (à déterminer)

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

11 novembre

L10

Au menu : je serai remplacé par (à déterminer) qui vous proposera (à déterminer)

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

14 novembre

T11

Au menu :

  • On reprend le rythme. Travaillez sur votre projet
  • J'aurai un petit truc d'optimisation amusant pour vous en cours de route, pour que vous soyez contents d'être venus en classe

18 novembre

L11

Au menu :

  • Quelques techniques de sérialisation binaire
  • S'il reste du temps, travaillez sur votre projet

21 novembre

T12

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.

25 novembre

L12

Au menu :

  • Travaillez sur votre projet

28 novembre

T13

Au menu :

  • Travaillez sur votre projet

2 décembre

L13

Au menu :

5 décembre

s/o

Journée d'examen pour les cours de français de la formation générale (cours suspendus)

9 décembre

L14

Au menu :

  • Tutorat sur demande. Je serai disponible à mon bureau. Récupérez d'EXPO-INFO et terminez ce que vous avez à terminer

12 décembre

T14

Au menu :

  • Remise de votre A.S.
  • Examen final écrit avec amour

Exceptionnellement, cette séance se tiendra deh 30 à 12 h.

Petits coups de pouces

Vous trouverez ici quelques documents, la plupart petits, qui peuvent vous donner un petit coup de pouce occasionnel.

Comment accéder à du code .NET à partir d'un client C++ natif

Introduction aux templates

Introduction aux foncteurs

Introduction aux conteneurs et aux itérateurs

Introduction aux expressions lambda (on écrit souvent λ plutôt que lambda)

Programmation générique appliquée

Exemple très simple de client par socket (C++)

Exemple très simple de serveur par socket (C++)

Exemple plus complet d'infrastructure de communication client/ serveur par socket (C++)

Dans la plupart des cas, vous trouverez de l'aide précieuse dans les sections Divers – C++, Au secours Pat et Trucs scouts du site.

Solutionnaires

Les solutionnaires aux exercices auxquels vous avez été confrontés et qui ont déjà été remis sur publiés ci-dessous

Cliquez sur cette cible si vous souhaitez un solutionnaire pour les exercices de révision

Cliquez sur cette cible pour avoir un exemple de tite_chaine respectant les consignes

Cliquez sur cette cible pour avoir un solutionnaire aux exercices de base

Cliquez sur cette cible si vous souhaitez un solutionnaire possible au problème des deux implémentations de mutex pour une même interface

Cliquez sur cette cible si vous souhaitez un solutionnaire possible au problème du clonage des monstres

Résultats des travaux pratiques

Ce qui suit collige les résultats des divers travaux pratiques réalisés au cours de la session. La moyenne est exprimée en pourcentage, peu importe l'échelle d'origine pour la correction des travaux en question. Les absent(e)s sont considéré(e)s avoir eu zéro.

La valeur de exprimée dans le tableau se veut un reflet du nombre de participant(e)s; cette valeur devrait normalement être équivalente au nombre d'étudiant(e)s inscrit(e)s au cours. En pratique, la colonne indique le nombre d'individus ayant remis le travail, alors que la colonne indique le nombre d'individus qui auraient dû remettre le travail. Malheureusement, un travail non remis signifie une note de zéro.

Travail Consignes Remise

Un mutex, deux implémentations

T01

L02

ListeEntiers et EX04

L01

T02

DLL (avec Rachid Kadouche)

L04

L04

Algos choisis

T02

T04

Clonage de monstres

T04

L05

Client / serveur de labyrinthes

T07

L09

Activité synthèse

À venir

L14

Pour les intéressé(e)s, voici :


Valid XHTML 1.0 Transitional

CSS Valide !