Un type synchronized_value<T>

La formule proposée ici est d'Anthony Williams, qui l'a proposée (en plus raffiné : http://www.drdobbs.com/cpp/enforcing-correct-mutex-usage-with-synch/225200269 pour sa proposition initiale à la communauté) pour fins de standardisation (voir ../../Liens/Evolution-Cplusplus--Liens.html pour des détails). J'aime bien ce qu'il propose, du fait qu'il s'agit d'une solution élégante (à mes yeux) à un problème a priori difficile.

Supposons que l'on souhaite synchroniser les accès à un objet, quelle que soit la complexité de son interface. Il s'agit d'un problème difficile, du fait que synchroniser les accès sur un objet influence typiquement son interface toute entière... Comment, par exemple, synchroniser les accès sur un std::vector<T>, sachant que ce type expose des services tels que begin() et end()? Un peu comme dans le cas d'un monitor<T>, on cherche alors à synchroniser les accès de manière plus globale que ne le suppose son interface.

La solution d'Anthony Williams est charmante : encapsuler l'objet à synchroniser dans une entité (un synchronized_value<T>) contenant l'objet et un mutex, mais ne permettre les accès à l'objet que par une entité (un update_guard<T>) qui garantit que, pendant son existence, elle est seule cliente de l'objet (synchronisant par une technique RAII l'objet à travers le mutex qui l'accompagne). Pour exposer les services de l'objet encapsulé, Williams suggère de passer par une surcharge de l'opérateur -> sur l'update_guard<T> ce qui a la qualité de permettre de solliciter n'importe quel membres accessible de l'objet auquel il contrôle l'accès.

Un exemple simple suit.

Pour permettre à synchronized_value<T> de donner des privilèges d'accès (par amitié) à update_guard<T>, nous exposons d'office une déclaration a priori de cette dernière

#include <mutex>
template <class>
   class update_guard;

Un synchronized_value<T>, pour l'essentiel, associe un T et un mutex. La plupart de ses services sont des constructeurs, ce qui est raisonnable du fait qu'il s'agit ici principalement d'une entité de regroupement et d'encapsulation; l'interface d'accès au T sera l'instance de la classe update_guard<T> qui assurera la synchronisation par la saine gestion du mutex.

template <class T>
   class synchronized_value
   {
   public:
      using value_type = T;
      synchronized_value(const synchronized_value&) = delete;
      synchronized_value& operator=(const synchronized_value&) = delete;
   private:
      mutable std::mutex m;
      T val;
   public:
      synchronized_value(const value_type &val)
         : val{val}
      {
      }
      template <class ... Args>
         synchronized_value(Args && ... args)
            : val(std::forward<Args>(args)...)
         {
         }
      synchronized_value(value_type &&val)
         : val{std::move(val)}
      {
      }
      synchronized_value(synchronized_value &&) = default;
      synchronized_value& operator=(synchronized_value &&) = default;
      friend class update_guard<value_type>;
   };

Un update_guard<T> est une entité RAII responsable de synchroniser un T logé dans un synchronized_value<T> et d'exposer les services de ce T une fois la synchronisation correctement réalisée.

template <class T>
   class update_guard
   {
      update_guard(const update_guard&) = delete;
      update_guard& operator=(const update_guard&) = delete;
      synchronized_value<T> &obj;
      std::unique_lock<std::mutex> verrou;
   public:
      update_guard(synchronized_value<T> &obj)
         : obj{ obj }, verrou{ obj.m }
      {
      }
      T* operator->()
         { return &obj.val; }
      const T* operator->() const
         { return &obj.val; }
   };

Enfin, voici un exemple d'utilisation. La portée de la variable guard définit une zone dans laquelle les accès au vector<int> caché dans sync_val sont exclusifs.

#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
int main()
{
   synchronized_value<vector<int>> sync_val = vector<int>{ 2, 3, 5, 7, 11 };
   {
      update_guard<vector<int>> guard{ sync_val };
      for_each(guard->begin(), guard->end(), [](int n) {
         cout << n << endl;
      });
   }
}

Valid XHTML 1.0 Transitional

CSS Valide !