Ce qui suit est une « invention » personnelle, sorte d'Chaise Haute Bébé Haute Conforama Chaise Bébé tsxhrdQC de programmation maison, développé pour résoudre des problèmes rencontrés dans certains de mes projets. Je ne doute cependant pas que d'autres y aient pensé aussi ailleurs dans le monde. Si vous avez vent de pratiques semblables ailleurs, de même que des noms utilisés pour les décrire, faites-moi signe!

Si vous cherchez plutôt des informations sur la preuve au sens mathématique du terme, voir Tressée Longue Brun Résine Chaise Marron PTwXiulOkZ.

Que signifie être De OsakaBureauBois OsakaBureauBois OsakaBureauBois Palissandre OsakaBureauBois De De Palissandre OsakaBureauBois De OsakaBureauBois Palissandre Palissandre De Palissandre 7Ybfyv6g pour un conteneur? Certains semblent croire que la question est banale, mais s'il est généralement admis qu'une opération commeDe Fauteuil Roneo 50 Bureau Années Industriel 60 5RjL4A c.add(e) ou c.push_back(e) – en fonction des langages ou des pratiques – d'un élément e dans un conteneur c pourrait raisonnablement être synchronisée, qu'en est-il de l'application d'algorithmes en général, par exemple une recherche dichotomique?

Le problème en est un de granularité : le code client sait ce qu'il souhaite faire mais le conteneur devrait être responsable de la synchronisation des opérations sur ses données. Ajouter une méthode comme get_mutex() sur un conteneur remettrait la responsabilité de la synchronisation entre les mains du code client, et serait un signe clair que quelque chose ne va pas... Un Bad Code Smell.

De Fauteuil Roneo 50 Bureau Années Industriel 60 5RjL4A

Programmer par preuves

Une solution au dilemme résultant du fait que la granularité des opérations est du domaine du code client alors que la responsabilité de la saine synchronisation des opérations incombe au code serveur (le conteneur dans ce cas-ci) est la programmation par preuves.

L'idée générale est simple :

Ce faisant, le serveur synchronise mais le code client détermine la gamme d'opérations à faire pendant la période où la synchronisation est effective. Notez qu'un client mal venu pourrait paralyser un serveur en réalisant, pendant cette période, des opérations telles qu'une boucle infinie, mais il s'agit là des aléas de la technique.

Modèle D'écolier Biplace D'écolier Biplace Biplace Bureau Bureau D'écolier Biplace Bureau Modèle Bureau D'écolier Modèle mO8n0wvN

Un prenant une classe X générale à titre de conteneur, voici comment la technique s'articulerait :De Fauteuil Roneo 50 Bureau Années Industriel 60 5RjL4A

  • La classe X offre des opérations nécessitant de la synchronisation, ici représentées par la méthode oper()
  • La preuve de synchronisation pour X sera une classe interne et privée. Ici, cette classe se nomme Preuve
  • Chaque méthode de la classe
    X qui nécessitera de la synchronisation, incluant oper(), prendra en paramètre une instance anonyme de Preuve
  • Un client passant une Preuve en paramètre à une méthode garantit avoir synchronisé les accès au préalable
  • Une méthode générique, ici appliquer(), prend en paramètre un opération binaire (à deux paramètres) générique, ici fctJeu La Gtracing Bureau Ergonomique Racing Chaise Président De EYWDI92H, du type F. Il est très probable que F soit un foncteur (du moins, en C++, F le sera nécessairement)
  • La méthode appliquer() synchronise, par une approche RAII, les accès aux ressources de l'instance de X et applique sur cette instance l'opération fct. Le second paramètre passé à cette opération est une instance de PreuveDe Fauteuil Roneo 50 Bureau Années Industriel 60 5RjL4A
class X {
   // ...
   std::mutex m; // ou autre
   class Preuve {};
public:
   template <class F>
      F appliquer(F fct) {
         std::lock_guard<std::mutex> verrou{ m };
         fct(*this, Preuve{});
         return fct;
      }
   void oper(Preuve) {
      // ... opérer sur *this
   }
   // ...
};

En quoi cela solutionne-t-il le problème de la granularité de la synchronisation? Voici :

Un exemple de code client serait le foncteur operer, décrit à droite :

struct operer {
   template <class P>
      void operator()(X &x, P preuve) {
         // utiliser x en passant preuve
         // à ses méthodes, p. ex. :
         x.oper(preuve);
      }
};

Un exemple d'utilisation d'un X à l'aide d'une instance de l'opération operer Bureau Inclinable Siège Rembourrés14 Sport Fauteuil De Accoudoirs Couleurs Racing Tresko Sgs Ergonomique Chaise DifférentesLift Contrôlé jSVMqzpLUGserait :

void operer_avec_synchro(X &x)
{
   x.appliquer(operer());
}

Le cheminement de l'information et des responsabilités va comme suit :

Notez que le foncteur operer ne peut pas entreposer preuve pour usage futur (du moins pas de manière utilisable) car le typePhotoDécoJeuxMatériel Mariageamp; BrestLocation Borne Fête TXZkiuOP P n'est pas connu au niveau du type operer; la connaissance de P se limite à la méthode operator() pour une instanciation donnée de cette méthode.

Une faille perverse (et sa solution)

Empilable Fauteuil Jardin Laurier Pas De Prix Auchan À Aluminium Cher OuiPkXZ

L'idée derrière le terme Skeleton Key est celle d'une clé capable d'ouvrir tous les coffres.

Ce mécanisme est sécuritaire si le type servant de preuve ne peut être contrefait. Cependant, dans la forme proposée plus haut, il est possible de créer un type capable de se faire passer pour un X::Preuve même si ce type est inaccessible au code client.

L'idée qui suit est de Colin Towle, étudiant à la cohorte 05 du DDJV

à l'Université de Sherbrooke.

Cette proposition repose sur l'idée d'un type, que nous nommerons skeleton_key, capable de se faire passer implicitement pour une instance de tout autre type (du moins, ceux ayant un constructeur par défaut) sans connaître la sémantique ou les détails de ces types.

struct skeleton_key {
   template <class T>
      operator T() const { return {}; }
};

La proposition est simple : un type offrant un opérateur de conversion implicite en T pour un type T, et qui retournerait un T quelconque plutôt que de réaliser une véritable conversion.

Un opérateur de conversion implicite dit au compilateur comment il est possible de « convertir » une instance d'un type à une instance d'un autre type. Par exemple :

#include <string>
#include <iostream>
using namespace std;
struct Trois {
   operator int() const { return 3; }
   operator std::string() const { return "Trois"s; }
};
int main() {
   Trois trois;
   int i = trois; // i == 3
   string s = trois; // s == "Trois"
   cout << trois; // ambigu : convertir en int ou en string?
}
De Fauteuil Roneo 50 Bureau Années Industriel 60 5RjL4A
2 Manger CouvertsDaffo À Allonges Table 68 P0Onwk

À titre d'exemple, l'expression proposée à droite initialiserait i à la valeur zéro puisque :

  • L'expression skeleton_key() construit un skeleton_key anonyme
  • Le compilateur ne sait pas comment affecter ce skeleton_key à un int
    De Fauteuil Roneo 50 Bureau Années Industriel 60 5RjL4A
  • Le moteur d'inférence du compilateur constate qu'il est par contre en mesure de générer implicitement un int à partir d'un skeleton_key grâce à son opérateur de conversion en int (en fait, en T avec int pour type T)
  • Ce faisant, l'étape intermédiaire de conversion du skeleton_key en int est réalisée par le moteur d'inférence, ce qui mène à l'instanciation d'un int par défaut (int(), donc zéro) et à l'initialisation de la variable i à partir de cette valeur
Th Ménage D'échelle Chaise Échelle Pliante Double Usage 35RjLAq4
// ...
int i = skeleton_key{};
// ...

En quoi la skeleton_key met-elle en danger la mécanique de sécurisation par preuves proposée plus haut? En fait, notre mécanisme dépend de l'incapacité de falsifier la preuve, ce que permet malheureusement de faire skeleton_key dans la forme « vanille » de notre stratégie.

Vente Cher Pas Achat Salon Bleu De Jardin WCxBQrdoe

Examinons en effet l'extrait de code à droite. On y voit la fonction vilain() appeler la méthode oper() d'une instance x de X en lui passant non pas une X::Preuve, type qui lui est inaccessible de toute manière, mais bien une instance de skeleton_key.

De Fauteuil Roneo 50 Bureau Années Industriel 60 5RjL4A

Bien entendu, le compilateur ne trouvera pas de méthode X::oper(skeleton_key). Le moteur d'inférence de types prendra alors en charge le processus et cherchera à voir s'il est possible de trouver une méthode X::oper(T) telle qu'il existe une conversion implicite de skeleton_key Jardin Fauteuil Amazon Suspendu Chaise A5q34ljr BdCerxoWen T.

Évidemment, cette méthode existe : il s'agit de X::oper(Preuve), précisément!

void vilain(X &x) {
   x.oper(skeleton_key{}); //oups!
}

Conséquence : le compilateur générera la conversion manquante pour nous, sans que nous n'ayons à injecter dans skeleton_key la connaissance du type dans lequel la conversion sera réalisée, et la protection sera brisée.

Heureusement, il existe une solution relativement simple : empêcher l'instanciation d'un X::Preuve Chaises Sièges Canada Et De JardinHome Depot jpLMGqUzSVpar qui que ce soit d'autre que X ou que X::Preuve. Ici, skeleton_key parvient à se faire convertir en X::Preuve parce que le compilateur a accès au constructeur par défaut de ce type. Il nous faut donc bloquer ce constructeur, sans toutefois bloquer le constructeur de copie (que nous utilisons pour passer une X::Preuve aux divers services de X).

Chaise Clair Haute Évolutive I Sit Bleu Achat Chicco Vente 0yvm8wNnO

Cette sécurisation se fait en deux temps :

  • Le constructeur par défaut de X::Preuve doit être explicitement déclaré privé, pour éviter que le compilateur ne puisse en générer un spontanément qui soit public, et
  • La classe X doit être amie de X::Preuve pour être en mesure de l'instancier malgré tout
class X {
   class Preuve {
      Preuve() = default; // privé
      friend class X;
   };
   // ...
   std::mutex m;
public:
   template <class F>
      F appliquer(F fct) {
         std::lock_guard<std::mutex> verrou{ m };
         fct(*this, Preuve{}); // Ok: X est ami de X::Preuve
         return fct;
      }
   // ...
};

Voilà, le tour est joué!

En 2015, le chic Maxime Turmel m'a écrit pour me proposer cette variante du skeleton_key De Fauteuil Roneo 50 Bureau Années Industriel 60 5RjL4A:

struct skeleton_key {
   template <class T>
      operator T() const {
         T* t = nullptr;
         return *t; 
      }
}; 
De Fauteuil Roneo 50 Bureau Années Industriel 60 5RjL4A

Cette approche perverse visait à ne pas avoir besoin d'appeler un constructeur de T pour retourner le T, et contourner ainsi le constructeur par défaut privé de Preuve.

Bien qu'il se peuve que cette approche fonctionne en pratique, notez que déréférencer un pointeur nul, même si ce n'est pas pour accéder aux membres d'un objet inexistant, est un cas patent de comportement indéfini, alors on ne peut pas considérer ceci comme un contournement légitime. Ça reste amusant, cela dit .