Exemple simple de délégué (sans allocation dynamique de mémoire)

Un délégué est un objet capable de « contenir » une entité susceptible d'être appelée, qu'il s'agisse d'une fonction globale, d'une méthode de classe ou d'une méthode d'instance (pour une instance donnée). Je présume qu'on pourrait aussi y déposer une λ mais je n'y ai pas suffisamment réfléchi pour être en mesure (a) de l'affirmer ou (b) d'offrir un exemple opérationnel.

L'exemple proposé ci-dessus applique un variante de l'idiome pImpl avec une strate utilisable (delegue), une strate d'abstraction (invoquable, qui est elle-même Clonable) et une strate d'implémentation (fonction et methode_instance). La strate utilisable est une classe englobante et toutes les autres sont privées et internes. La classe Clonable est une variante de l'implémentation plus classique, et utilise l'allocation positionnelle.

Cette version évite l'allocation dynamique de mémoire. Nous y implémentons la Sainte-Trinité, mais pas la sémantique de mouvement.

Dans cette implémentation, le recours à new prend un temps temps constant (complexité ) dû au fait que le lieu où sera construit l'objet est connu au préalable. La sémantique de mouvement serait pas contre plus complexe à implémenter (et son efficacité pourrait être discutable).

#include <type_traits>
#include <utility>
#include <cassert>
#include <algorithm>
#include <tuple>
// utilitaires
template <class> struct max_sizeof;
template <class ... Ts> struct max_sizeof<std::tuple<Ts...>> {
   static constexpr auto value = std::max({ sizeof(Ts)... });
};
template <class T> constexpr auto max_sizeof_v = max_sizeof<T>::value;
template <class> struct max_alignof;
template <class ... Ts> struct max_alignof<std::tuple<Ts...>> {
   static constexpr auto value = std::max({ alignof(Ts)... });
};
template <class T> constexpr auto max_alignof_v = max_alignof<T>::value;

// idiome ~pimpl en trois temps:
// - delegue<R,A> sera la strate utilisable
template <class R, class A>
class delegue {
   // - invoquable sera l'abstraction polymorphique
   struct invoquable {
      virtual R invoquer(A) = 0;
      virtual invoquable* cloner(void *) const = 0;
      virtual ~invoquable() = default;
   };
   // - fonction sera une implémentation possible
   class fonction : public invoquable {
      R(*ptr)(A);
   public:
      fonction(R(*ptr)(A)) noexcept : ptr{ ptr } {
      }
      R invoquer(A arg) {
         return ptr(arg);
      }
      fonction * cloner(void *p) const override {
         return new (p) fonction{ *this };
      }
   };
   // - methode_instance<T> sera une autre implémentation possible
   template <class T>
   class methode_instance : public invoquable {
      T *inst;
      R(T::*ptr)(A);
   public:
      methode_instance(T *inst, R(T::*ptr)(A)) noexcept : inst{ inst }, ptr{ ptr } {
      }
      R invoquer(A arg) {
         return (inst->*ptr)(arg);
      }
      methode_instance* cloner(void *p) const override {
         return new (p) methode_instance{ *this };
      }
   };
   // - methode_instance_const<T> sera une autre implémentation possible
   template <class T>
   class methode_instance_const : public invoquable {
      T *inst;
      R(T::*ptr)(A) const;
   public:
      methode_instance_const(T *inst, R(T::*ptr)(A) const) noexcept : inst{ inst }, ptr{ ptr } {
      }
      R invoquer(A arg) {
         return (inst->*ptr)(arg);
      }
      methode_instance_const* cloner(void *p) const override {
         return new (p) methode_instance_const{ *this };
      }
   };
   using types = std::tuple<
      fonction, methode_instance<delegue<R,A>>, methode_instance_const<delegue<R, A>>
   >;
   // - ptr cachera au delegue laquelle des implémentations est utilisée
   alignas(max_alignof_v<types>) char buf[max_sizeof_v<types>];
   invoquable *ptr{};
   void *internal_buffer() {
      return buf;
   }
   invoquable *internal_invoker() {
      return reinterpret_cast<invoquable*>(buf);
   }
public:
   bool empty() const noexcept {
      return !ptr;
   }
   delegue() = default;
   // si on construit un delegue autour d'une fonction
   delegue(R(*fct)(A)) : ptr{ new (internal_buffer()) fonction{ fct } } {
   }
   // si on construit un delegue autour d'une méthode d'instance
   template<class T>
   delegue(T *inst, R(T::*fct)(A)) : ptr{ new (internal_buffer()) methode_instance<T>{inst, fct} } {
   }
   // si on construit un delegue autour d'une méthode d'instance const
   template<class T>
   delegue(T *inst, R(T::*fct)(A) const) : ptr{ new (internal_buffer()) methode_instance_const<T>{inst, fct} } {
   }
   // Sainte-Trinité
   delegue(const delegue &autre) : ptr{ autre.empty()? nullptr : autre.ptr->cloner(internal_buffer()) } {
   }
   // attention : pas exception-safe
   delegue &operator=(const delegue &autre) {
      if (&autre != this) {
         ptr->~invoquable();
         ptr = autre.empty()? nullptr : autre.ptr->cloner(internal_buffer());
      }
      return *this;
   }
   ~delegue()  {
      if (ptr) ptr->~invoquable();
   }
   // invocation
   R operator()(A arg) {
      assert(ptr);
      return ptr->invoquer(arg);
   }
};

class X {
   double val;
public:
   X(double val) : val{ val } {
   }
   double m(int x) {
      return x + val++;
   }
   double mc(int x) const {
      return x + val;
   }
};

double f(int x) {
   return static_cast<double>(x) / 2.0;
}

#include <iostream>
int main() {
   using namespace std;
   delegue<double, int> d = f;
   cout << d(3) << endl;
   X x{ 0.5 };
   d = delegue<double, int>(&x, &X::m);
   cout << d(3) << endl;
   d = delegue<double, int>(&x, &X::mc);
   cout << d(3) << endl;
}

Valid XHTML 1.0 Transitional

CSS Valide !