Code de grande personne – bloquer la copie

Cet article s'inspire d'une des stratégies typiques de la bibliothèque Boost. Des thématiques passionnantes et stimulantes pour l'esprit, mais définitivement du code de grande personne (quoique cet article soit beaucoup moins lourd sur l'esprit que d'autres de la même nature).

Cet article présume que vous avez lu au préalable celui portant sur le schéma de conception singleton, ou du moins que vous êtes en train de le lire.

Il est assez fréquent qu'un programmeur souhaite empêcher la copie des instances d'une classe donnée, en particulier dans le cas d'un singleton.

La technique pour y arriver avec C++ 03 est relativement simple :

La technique pour y arriver depuis C++ 11 est encore plus simple :

Dans le cas d'un singleton jouant le rôle d'un générateur de nombres séquentiels unique au programme. Le code typique serait :

class GenerateurSequentiel {
public:
   using value_type = int;
private:
   value_type cur;
   //
   // Bloquer la copie
   //
public:
   GenerateurSequentiel& operator=(const GenerateurSequentiel&) = delete;
   GenerateurSequentiel(const GenerateurSequentiel&) = delete;
private:
   GenerateurSequentiel() noexcept : cur{} {
   }
public:
   static GenerateurSequentiel &get() noexcept {
      static GenerateurSequentiel singleton;
      return singleton;
   }
   value_type prochain() noexcept {
      return ++cur;
   }
};
//
// Passage de paramètre par valeur (notez que
// le paramètre n'est même pas utilisé,
// donc pas besoin de lui donner de nom!)
//
void f(GenerateurSequentiel) {
}
#include <iostream>
int main() {
   using namespace std;
   auto &gen = GenerateurSequentiel::get();
   cout << gen.prochain() << ' '
        << gen.prochain() << ' '
        << gen.prochain() << endl;
   // ceci serait illégal, impliquant une copie
   // f(GenerateurSequentiel::get());
}

Notez au passage que ce programme affichera 3 2 1 plutôt que 1 2 3. Je vous invite à lire cette section du musée des horreurs pour une explication du phénomène.

La technique par laquelle un objet devient impossible à  copier est la même peu importe le type. On pourrait même lui donner un nom. Dans la bibliothèque Boost, le nom boost::noncopyable a été choisi. J'utiliserai Incopiable ci-dessous pour une version allégée de la technique.

La technique

Ce qui suit montre la technique sous un angle complexe, sujet à  intégration avec d'autres techniques. En général, ceci suffit amplement, en C++ 03...

class Incopiable {
   Incopiable(const Incopiable&);
   Incopiable& operator=(const Incopiable&);
protected:
   Incopiable() {
   }
   ~Incopiable() {
   }
};

... ou (mieux) en C++ 11 :

class Incopiable {
public:
   Incopiable(const Incopiable&) = delete;
   Incopiable& operator=(const Incopiable&) = delete;
protected:
   constexpr Incopiable() = default;
   ~Incopiable() = default;
};

La déclaration publique des opérations supprimées permet dans ce cas au compilateur de donner de meilleurs diagnostics : plutôt que d'indiquer que la fonction est inaccessible, il indiquera qu'elle est supprimée.

La technique repose sur l'héritage, idéalement l'héritage privé. Être Incopiable devrait être une particularité d'implémentation; conséquemment, Incopiable n'exposera aucune méthode polymorphique et ne contiendra aucun attribut d'instance (ce qui l'assujettira au Empty Base Class Optimization, ou EBCO ).

Ainsi, en résumé :

Pour faciliter l'insertion de classes dérivant de Incopiable dans des hiérarchies de classes à  haute performance et à  faible coût, nous appliquerons aussi la technique de l'enchaînement (potentiel) de parents, bien que ce ne soit pas essentiel au propos.

Le code suit, dans une forme qui permet le Base Class Chaining.

class Vide {};

template <class B = Vide>
   class Incopiable : B {
   public:
      Incopiable(const Incopiable&) = delete;
      Incopiable& operator=(const Incopiable&) = delete;
   protected:
      constexpr Incopiable() = default;
      ~Incopiable() = default;
   };
class GenerateurSequentiel : Incopiable<> {
public:
   using value_type = int;
private:
   value_type cur;
   constexpr GenerateurSequentiel() noexcept : cur{} {
   }
public:
   static GenerateurSequentiel &get() noexcept {
      static GenerateurSequentiel singleton;
      return singleton;
   }
   value_type prochain() noexcept {
      return ++cur;
   }
};

//
// Passage de paramètre par valeur (notez que
// le paramètre n'est même pas utilisé,
// donc pas besoin de lui donner de nom!)
//
void f(GenerateurSequentiel) {
}
#include <iostream>
int main() {
   using namespace std;
   auto &gen = GenerateurSequentiel::get();
   cout << gen.prochain() << ' '
        << gen.prochain() << ' '
        << gen.prochain() << endl;
   // ceci serait illégal, impliquant une copie
   // f(GenerateurSequentiel::get());
}

Quelques brèves remarques :


Valid XHTML 1.0 Transitional

CSS Valide !