Vous trouverez ici quelques documents et quelques liens pouvant, je l'espère,
vous être utiles.
Date |
Séance |
Contenu |
Vendredi 12 janvier AM |
S00 |
Au menu :
Notez que nous examinons dans ce cours des techniques portables vers
plusieurs plateformes et plusieurs compilateurs, mais que C++ 11
offre maintenant une
API pleinement portable de threading
et de synchronisation.
Vous en
verrez un exemple ici, et je vous montrerai comment en implémenter les
bases vous-mêmes si vous le souhaitez.
Cela dit, il est possible (pour ne pas dire probable) que cette
API
ne soit pas disponible sur
certaines des plateformes que vous rencontrerez dans
l'industrie, du moins à court terme. Ainsi, ne vous en faites pas : en examinant des manières
de procéder sans cette
API, nous ne perdons pas notre temps.
Dans les notes de cours :
- Pour l'essentiel, ce qui a été couvert aujourd'hui apparaît dans
CPA – Volume 01, pp. ≈9-41
|
Vendredi 19 janvier AM |
S01 |
Au menu :
Dans les notes de cours :
- Les objets volatiles sont expliqués dans
CPA – Volume 01, pp. ≈99-123
- Un tableau résumant les impacts du mot volatile
apparaît dans
CPA – Volume 01, p. 117
- À propos de l'atomicité, voir Atomicite.pdf (document qui demande à être retravaillé, mais c'est mieux que rien)
J'ai montré un petit exemple d'implémentation réduite de std::async()
à
titre illustratif, acceptant une fonction int(*)(int)
et un paramètre int
puis retournant une future<int>. Voici une version un peu plus complète (je n'ai pas tenu
compte des politiques de démarrage comme
std::launch_async et std::launch_defer) :
template <class T, class F, class ... Args>
auto async(F f, Args && ... args) -> decltype(f(std::forward<Args>(args)...)) {
promise<T> ze_promesse;
future<T> ze_future = ze_promesse.get_future();
thread th{ [](promise<T>&& p, F f, Args && ... args) {
try {
p.set_value(f(std::forward<Args>(args)...));
}
catch (...) {
p.set_exception(current_exception());
}
}, std::move(ze_promesse), f, std::forward<Args>(args)... };
th.detach();
return ze_future;
}
Ceci ne signifie pas que votre implémentation soit écrite exactement
comme ceci, mais c'est l'idée.
J'ai aussi fait une petite activité pour compter les vilains dans une
carte de grande taille, en séquentiel puis en parallèle. Tel que
mentionné en classe, ne faites pas circuler cet exemple (c'est un
exercice que j'utilise à l'occasion quand je donne des formations en
entreprise). Le code suit :
#include <mutex>
#include <thread>
#include <vector>
#include <iostream>
#include <chrono>
#include <locale>
#include <future>
#include <random>
#include <algorithm>
#include <numeric>
using namespace std;
using namespace std::chrono;
template <class F, class ... Args>
auto tester(F f, Args &&... args) {
auto pre = high_resolution_clock::now();
auto res = f(std::forward<Args>(args)...);
auto post = high_resolution_clock::now();
return make_pair(res, post - pre);
}
enum class Case : unsigned char {
Vide, Heros, Mur, Vilain, Bibitte
};
int main() {
locale::global(locale{ "" });
enum { LARGEUR = 25'000, HAUTEUR = 25'000 };
vector<Case> carte(LARGEUR * HAUTEUR, Case::Vide);
random_device rd;
mt19937 prng{ rd() };
uniform_int_distribution<int> d100{ 1,100 }, d4{ 1,4 };
auto temps_init = tester([&] {
generate(begin(carte), end(carte), [&] {
return d100(prng) <= 85 ? Case::Vide :
static_cast<Case>(d4(prng));
});
return 0;
});
cout << "Initialisation complétée en "
<< duration_cast<milliseconds>(temps_init.second).count()
<< " ms." << endl;
cout << "Nb pas gentils (séquentiel) : ";
auto seq = tester([&] {
return count_if(begin(carte), end(carte), [](Case c) {
return c == Case::Bibitte || c == Case::Vilain;
});
});
cout << seq.first << " obtenu en "
<< duration_cast<milliseconds>(seq.second).count()
<< " ms." << endl;
//
// Ok, on y va...
//
cout << "Nb pas gentils (parallèle) : ";
auto para = tester([&] {
auto ncoeurs = thread::hardware_concurrency();
const auto taille_bloc = carte.size() / ncoeurs;
vector<future<int>> v;
for (decltype(ncoeurs) i = 0; i != ncoeurs - 1; ++i)
v.emplace_back(async([deb = begin(carte) + i * taille_bloc,
fin = begin(carte) + (i + 1) * taille_bloc]{
return count_if(deb, fin, [](Case c) {
return c == Case::Bibitte || c == Case::Vilain;
});
}));
auto n = count_if(begin(carte) + (ncoeurs - 1) * taille_bloc,
end(carte), [](Case c) {
return c == Case::Bibitte || c == Case::Vilain;
});
return accumulate(begin(v), end(v), n, [](int so_far, future<int> &f) {
return so_far + f.get();
});
});
cout << para.first << " obtenu en "
<< duration_cast<milliseconds>(para.second).count()
<< " ms." << endl;
}
Enfin, j'ai fait une démonstration des coûts du faux-partage
(attention : compilez en 64 bits dû à la
taille du vecteur). Le code
suit :
#include <mutex>
#include <thread>
#include <vector>
#include <iostream>
#include <chrono>
#include <locale>
#include <future>
#include <random>
#include <algorithm>
#include <numeric>
using namespace std;
using namespace std::chrono;
template <class F, class ... Args>
auto tester(F f, Args &&... args) {
auto pre = high_resolution_clock::now();
auto res = f(std::forward<Args>(args)...);
auto post = high_resolution_clock::now();
return make_pair(res, post - pre);
}
int main() {
locale::global(locale{ "" });
enum { N = 25'000 };
vector<int> mat(N * N);
auto taille_bloc = mat.size() / 8; // hardware_concurrency
iota(begin(mat), end(mat), 1); // approx. la moitié est impaire
auto r0 = tester([&] {
int nimpairs[8]{}; // initialisés à zéro; 8 == hardware_concurrency
vector<thread> th;
for (int i = 0; i != 8; ++i)
th.emplace_back([&, i, taille_bloc] {
for (int j = i * taille_bloc; j != (i + 1) * taille_bloc; ++j)
if (mat[j] % 2 != 0)
nimpairs[i]++;
});
for (auto & thr : th) thr.join();
return accumulate(begin(nimpairs), end(nimpairs), 0);
});
cout << "Par. Nb impairs au total : " << r0.first
<< " obtenu en " << duration_cast<milliseconds>(r0.second).count()
<< " ms." << endl;
auto r1 = tester([&] {
int nimpairs = 0;
for (size_t j = 0; j != mat.size(); ++j)
if (mat[j] % 2 != 0)
nimpairs++;
return nimpairs;
});
cout << "Seq. Nb impairs au total : " << r1.first
<< " obtenu en " << duration_cast<milliseconds>(r1.second).count()
<< " ms." << endl;
auto r2 = tester([&] {
int nimpairs[8]{}; // initialisés à zéro; 8 == hardware_concurrency
vector<thread> th;
for (int i = 0; i != 8; ++i)
th.emplace_back([&, i, taille_bloc] {
int n = 0;
for (int j = i * taille_bloc; j != (i + 1) * taille_bloc; ++j)
if (mat[j] % 2 != 0)
n++;
nimpairs[i] = n;
});
for (auto & thr : th) thr.join();
return accumulate(begin(nimpairs), end(nimpairs), 0);
});
cout << "Par. Nb impairs au total : " << r2.first
<< " obtenu en " << duration_cast<milliseconds>(r2.second).count()
<< " ms." << endl;
}
|
Mardi 23 janvier PM |
S02 |
Au menu :
- Q00
- Les sockets de type
flux :
- exploration brève du modèle de programmation
- examen rapide des distinctions entre le modèle
BSD de base, qui est universel, et certaines implémentations
OO
construites par-dessus
- réflexion sur les limites de ce qui peut être transigé entre
deux homologues situés dans des espaces adressables distincts
- réflexion aussi sur les distinctions structurelles entre les
architectures matérielles, en particulier en ce qui a trait à la
structure interne des entiers
Dans les notes de cours :
- Les sockets de type flux sont décrits dans CPA – Volume 02, pp. ≈6-36
|
Vendredi 26 janvier AM |
S03 |
Au menu, activité pratique :
- En équipe de deux personnes, implémentez un système client/ serveur
où le serveur peut gérer plusieurs clients de manière concurrente
- Le client devra envoyer au serveur un nom de fichier (vous devez
supporter à la fois les fichiers « texte » et « binaires »). Le nom peut
inclure un chemin (p. ex. :
"c:\machin\truc\yo man.jpeg" ou
"../projet.sln"), et les répertoires relatifs seront considérés
sur la base du répertoire de travail du serveur
- Le serveur devra consommer ce nom, puis transférer le contenu du
fichier en question au client
- Le client devra consommer le contenu du fichier, et écrire ce fichier
dans son propre répertoire de travail (excluant le chemin s'il y a lieu)
- On doit pouvoir vérifier que le fichier reçu et écrit sur le poste du
client est identique au fichier d'origine (ça peut être aussi simple que
de l'ouvrir
)
- Présentez à votre chic prof le code source du client et du serveur pour
rétroaction sur le code (on peut faire cela en classe si vous le
souhaitez)
Je serai avec vous, en soutien. L'idée ici est de s'assurer que vous
êtes en mesure de bien gérer plusieurs séances concurrentes de transfert
de fichiers, que vous gérez adéquatement les ressources associées à ces
transactions, et que vous êtes familiarisés avec les sockets de
type flux.
J'ai préparé quelques exemples de type « solutionnaire » pour vous aider :
En espérant que le tout vous soit utile!
|
Mardi 30 janvier PM |
S04 |
Au menu :
- Q01
- Les sockets de type datagramme :
- comparaison avec les sockets de type flux
- différences protocolaires
- combiner struct et union
(les
union
étiquetés)
- portabilité des données
- Considérations de sérialisation
(texte)
Voici un exemple simple de sérialisation brute adaptative :
#define NOMINMAX // car windows.h, inclus « par la bande », et un vilain garnement
#include <winsock2.h>
#pragma comment(lib,"Ws2_32.lib")
#include <iostream>
#include <algorithm>
#include <cassert>
#include <string>
#include <type_traits>
#include <iterator>
using namespace std;
template <class T>
T normaliser(T val) { return val; }
short normaliser(short val) { return htons(val); }
int normaliser(int val) { return htonl(val); }
long normaliser(long val) { return htonl(val); }
template <class T>
enable_if_t<is_integral<T>::value, char *>
serialiser_brut(const T &val, char *p) {
static_assert(is_trivially_copyable_v<T>);
auto valeur = normaliser(val);
copy(reinterpret_cast<const char *>(&valeur),
reinterpret_cast<const char *>(&valeur + 1), p);
return p + sizeof(T);
}
template <class T>
enable_if_t<!is_integral<T>::value, char *>
serialiser_brut(const T &val, char *p) {
static_assert(is_trivially_copyable_v<T>);
copy(reinterpret_cast<const char *>(&val),
reinterpret_cast<const char *>(&val + 1), p);
return p + sizeof(T);
}
int main() {
float f = 3.14159f;
long lg = 3L;
char c = 'A';
string s = "J'aime mon prof";
char buf[sizeof(f) + sizeof(lg) + sizeof(c)] = {};
auto p = begin(buf);
p = serialiser_brut(f, p);
p = serialiser_brut(lg, p);
p = serialiser_brut(c, p);
// p = serialiser_brut(s, p); // illégal!
assert(p == end(buf));
}
Dans les notes de cours :
- Les sockets de type datagramme sont décrits dans CPA – Volume 02, pp. ≈61-70
- La sérialisation est discutée en détail dans CPA – Volume 03, pp. ≈10-46
|
Vendredi 2 février AM |
S05 |
Au menu :
|
Mardi 6 février PM |
S06 |
Au menu :
- Q04
- Construction d'un
pointeur intelligent implémentant une sémantique
de partage – sorte de
shared_ptr maison – pour voir et comprendre ce
que cela implique
- ça semble court comme menu, mais c'est vraiment
rien de simple
|
La semaine du 12 au 16
février, notre cours
fait relâche. Bon travail sur votre projet, les amis! |
Mardi 20 février PM |
S07 |
Au menu :
|
Vendredi 23 février AM |
S08 |
Au menu :
J'ai écrit un délégué « live » avec vous en classe, avec
une sémantique différente de celle mise de l'avant par
std::function. Le code suit, au
cas où ce serait utile :
template <class R, class A>
class delegue {
struct Appelable {
virtual R appeler(A) const = 0;
virtual ~Appelable() = default;
virtual Appelable *cloner() const = 0;
};
Appelable *p;
struct PtrFonction : Appelable {
R(*pf)(A);
PtrFonction(R(*pf)(A)) noexcept : pf{ pf } {
}
R appeler(A arg) const override{
return pf(arg);
}
PtrFonction *cloner() const override {
return new PtrFonction{ *this };
}
};
template <class T>
struct PtrMethode : Appelable {
R(T::*pm)(A) const;
T obj;
PtrMethode(T obj, R(T::*pm)(A) const) : obj{ obj }, pm{ pm } {
}
R appeler(A arg) const override {
return (obj.*pm)(arg);
}
PtrMethode *cloner() const override {
return new PtrMethode{ *this };
}
};
template <class F>
struct Foncteur : Appelable {
F obj;
Foncteur(F obj) : obj{ obj } {
}
R appeler(A arg) const override {
return obj(arg);
}
Foncteur *cloner() const override {
return new Foncteur{ *this };
}
};
public:
delegue() noexcept : p{} {
}
bool empty() const noexcept {
return !p;
}
delegue(R(*pf)(A)) : p{ new PtrFonction{ pf } } {
}
template <class T>
delegue(T obj, R(T::*pm)(A) const) : p{ new PtrMethode<T>{ obj, pm } } {
}
template <class F>
delegue(F f) : p{ new Foncteur<F>{ f } } {
}
~delegue() {
delete p;
}
delegue(const delegue &autre) : p{ autre.p ? autre.p->cloner() : nullptr } {
}
void swap(delegue &autre) {
using std::swap;
swap(p, autre.p);
}
delegue& operator=(const delegue &autre) {
delegue{ autre }.swap(*this);
return *this;
}
R operator()(A arg) const {
return p->appeler(arg);
}
};
int f(double x) {
return static_cast<int>(x);
}
struct F {
int operator()(double x) const {
return static_cast<int>(x * 2);
}
};
struct Obj {
int meth(double x) const {
return static_cast<int>(-x);
}
};
#include <iostream>
using namespace std;
int main() {
delegue<int, double> del = f;
cout << del(3.5) << endl; // 3
del = F{};
cout << del(3.5) << endl; // 7
Obj obj;
del = delegue<int, double>{ obj, &Obj::meth };
cout << del(3.5) << endl; // -3
}
|
Mardi 27 février PM |
S09 |
Au menu :
- Q06
- Comprendre le type
std::tuple
- Facettes
- Petit défi technique : implantons un mécanisme de facettes
non-intrusive (au sens où il n'oblige pas les facettes
à dériver
elles-mêmes de Facette) tel que le programme ci-dessous fonctionne, n'entraîne pas
de fuites de ressources, et offre l'affichage attendu. Le code client
imposé est :
#include "FacetteServer.h"
#include <iostream>
struct Texture {
const char *getTextureName() const noexcept {
return "Je suis un nom de texture";
}
};
struct TextureManager {
Texture getTexture() const noexcept {
return {};
}
};
struct Sound {
const char *getFileName() const noexcept {
return "SomeSound.wav";
}
};
struct SoundManager {
Sound getSound() const noexcept {
return {};
}
};
int main() {
using namespace std;
auto &serveur = FacetteServer::get();
serveur.installer(TextureManager{});
serveur.installer(SoundManager{});
// ...
cout << utiliser_facette<SoundManager>(serveur).getSound().getFileName() << endl;
cout << utiliser_facette<TextureManager>(serveur).getTexture().getTextureName() << endl;
}
La sortie attendue est :
SomeSound.wav
Je suis un nom de texture
|
Mardi 6 mars PM |
S10 |
Au menu :
- Q07
- Diverses techniques d'optimisation, incluant la tristement (!)
célèbre
Duff's Device
- Quelques considérations architecturales globales, dont :
|
Vendredi 9 mars AM |
S11 |
Au menu :
- Une présentation du très chic Patrick Hubert,
d'Autodesk, qui dissertera entre autres
sur son vécu comme développeur dans le monde du jeu vidéo
et de l'intégration de code
Python
et de code
C++
- Une présentation du très chic
David Viens,
de Plogue.com, qui discutera de
son expérience particulière de dirigeant de petite entreprise
techno dans le monde de la musique et de
systèmes temps réel
|
La semaine du 12 mars au
16 mars, notre cours
fait relâche. Je serai à la rencontre du
WG21 à Jacksonville; voir
../../Sujets/Orthogonal/wg21-2018-Jacksonville.html si vous voulez suivre mes
aventures. |
Les semaines du 19 mars,
26 mars et 2 avril, notre cours
fait relâche. Bon travail sur votre projet, les amis. Prenez soin
de ne pas oublier tout ce que nous avons fait jusqu'ici! |
Mardi 10 avril PM
|
s/o |
Séance déplacée au 12 avril à 18 h 30 pour
profiter d'un conférencier spécial
|
Jeudi 12 avril PM (18 h 30) |
S12 |
Au menu :
- Séance spéciale de soirée, avec la présence virtuelle de Chris Spears.
Dans ses propres mots, sa notice biographique est :
- « I started programming on a TRS-80 with 4k of ram in the late 70's. I started teaching programming in high school in the 80's because there were no teachers who knew how to program at the school. In the late 80's I was teaching a crazy new language called
"C++" at a university and coding for MUDs. Around 1991 I started coding professionally for computer games and have been doing so ever since pretty much always as a lead programmer, tech director, or CTO »
- Les sujets prévus au menu sont, toujours dans ses mots :
- Tech stuff (PC, Consoles, MMO's, mobile, smart toys, Web games, and lots of cross platform stuff)
- Crowdfunding projects
- Crowdsourcing on projects
- Completely open development (we keep no secrets from the player and even post our daily standup notes)
- What it is like working with industry legends (Richard and Starr but previously I was director on a few Star Wars games including Lucas' pet project, Darth Maul)
- A topic I think might be sexy to college kids is data mining and how we use it in games
- Discuss the evolution of our project planning from waterfall and gant charts to Scrum to where we are today with just flexible agile
- Talk about pros and cons of working in the games industry since I have experience working as first party, contractor to a big publisher, as a VC funded startup, to a completely crowdfunded indie
- AND if desired, I can also talk about tech and how I've seen
development evolve over time in addition to various future tech stuff
- Vos questions seront les bienvenues, alors n'hésitez pas à vous
préparer en conséquence!
|
Mardi 17 avril PM |
S13 |
Au menu :
- Une présentation d'André Caron, programmeur chez Ubisoft, portant
sur les jeux en ligne et sur les problèmes de grande envergure :
- ce dont il s'agit
- leur place dans l'industrie
- enjeux et les particularités techniques intéressantes,
etc.
Changement de plan : André Caron est coincé au bureau pour une urgence
cette semaine et a dû se décommander.
- Q08
- Q09
- Rapport de voyage, en particulier les enjeux qui touchent
directement notre cours :
- Annotation [[no_unique_address]], utile en
particulier dans le cas des classes terminales
- Annotations [[likely]] et
[[unlikely]]
- Contrats
- Destroying operator delete
- <span>
- Itérateurs constexpr
- simd<T> pour le Parallelism TS v2
- Static Reflection TS
- Coroutines
et transfert de contrôle symétrique
- Aussi intéressants, mais d'un autre ordre :
- SG15
- SG16
- Dates et fuseaux horaires
- Down with typename!
- Destructeurs
constexpr
- Travaux sur la syntaxe concise des
concepts
- λ et capture variadique
- Affectation déstructurante (Structured
Bindings) et accessibilité
- J'ai appris que les qualifications d'accès voyagent dans le temps :
struct A {
protected:
struct B {
};
};
struct X : A::B, A {
};
int main() {
X x;
}
- Cas analogues au problème de l'arrêt
#include <cstdio>
#include <cstdlib>
void f() {
struct X {
~X() {
std::puts("unwound");
std::exit(0);
}
} x;
throw 0;
}
int main(int argc, char**) {
try {
f();
} catch (int) {
std::puts("caught");
}
}
- Quelques horreurs (toujours amusantes), par exemple :
struct A {
int n = A{}.n;
};
struct A { int x, y; };
A passthrough(A a) { return a; }
int main(void) {
A a;
a.x = 0;
return passthrough(a).x; // Oups! UB!
}
Pour le reste, on y va selon vos questions et besoins.
|
La semaine du 23 avril, notre cours
fait relâche. Bon travail sur votre projet, les amis! |
Mardi 1er mai PM
|
S14 |
Remise de
L01, puis un chic examen final vous
attend! |
Les moyennes des résultats obtenus aux questions quasi-hebdomadaires pour la
session en cours 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).
Vous constaterez que je ne vous impose pas de livraison de code. Un document
bref (disons 6-8 pages) par équipe suffira.
J'aimerais l'avoir le 22 février
du fait que c'est probablement déjà pensé et fait et que
ça peut demeurer succinct.
Ça vous va? Sinon, faites-moi signe...
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.
Sources de divers exemples qui
doivent être retouchés à la lueur de C++ 11 |
 |
Cliquez sur cette cible pour obtenir le code source de la
classe pattern_iterator, document
CPA – Volume
02 (ou encore cet
article).
|
 |
Cliquez sur cette cible pour obtenir le code à optimiser
pour EX00, série
04, document CPA – Volume 03.
|
 |
Cliquez sur cette cible pour obtenir le code complet de l'automate
déterministe à états finis, version
00, tel qu'il apparaît dans le document CPA – Volume 04.
|
 |
Cliquez sur cette cible pour obtenir le code complet de l'automate
déterministe à états finis, version
01, tel qu'il apparaît dans le document CPA – Volume 04.
|
 |
Cliquez sur cette cible pour obtenir le code complet de l'automate
déterministe à états finis, version
02, tel qu'il apparaît dans le document CPA – Volume 04.
|
 |
Cliquez sur cette cible pour obtenir le code complet de l'automate
déterministe à états finis, version
03, tel qu'il apparaît dans le document CPA – Volume 04.
|
 |
Cliquez sur cette cible pour obtenir le code complet de l'automate
déterministe à états finis, version
04, tel qu'il apparaît dans le document CPA – Volume 04.
|
 |
Cliquez sur cette cible pour obtenir le code complet de l'automate
déterministe à états finis, version
05, tel qu'il apparaît dans le document CPA – Volume 04.
|
 |
Cliquez sur cette cible pour obtenir le code complet de l'automate
déterministe à états finis, version
06, tel qu'il apparaît dans le document CPA – Volume 04.
|
 |
Cliquez sur cette cible pour obtenir le code complet du sélecteur
de conversions, tel qu'il apparaît dans le document CPA – Volume 04.
|