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.
Cette version utilise de l'allocation dynamique de mémoire. Ceci signifie qu'il nous faut implémenter la Sainte-Trinité, mais que la sémantique de mouvement y est banale à implémenter (ce que j'ai fait, évidemment).
Dans cette implémentation, le recours à new prend un temps indéterministe, mais que la sémantique de mouvement s'implémente en temps constant (complexité ).
#include <functional>
#include <utility>
#include <cassert>
#include <iostream>
#include <memory>
template <class R, class A>
class delegue {
struct invoquable {
virtual R invoquer(A) = 0;
virtual invoquable* cloner() const = 0;
virtual ~invoquable() = default;
};
class fonction : public invoquable {
R(*ptr)(A);
public:
fonction(R(*ptr)(A)) noexcept : ptr{ ptr } {
}
R invoquer(A arg) {
return ptr(arg);
}
fonction* cloner() const override {
return new fonction{ *this };
}
};
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() const override {
return new methode_instance{ *this };
}
};
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() const override {
return new methode_instance_const{ *this };
}
};
std::unique_ptr<invoquable> ptr;
public:
bool empty() const noexcept {
return !ptr;
}
delegue() = default;
delegue(R(*fct)(A)) : ptr{ new fonction{ fct } } {
}
template<class T>
delegue(T *inst, R(T::*fct)(A)) : ptr{ new methode_instance<T>{ inst, fct } } {
}
template<class T>
delegue(T *inst, R(T::*fct)(A) const) : ptr{ new methode_instance_const<T>{ inst, fct } } {
}
void swap(delegue &d) noexcept {
using std::swap;
swap(ptr, d.ptr);
}
delegue(const delegue &autre) : ptr{ autre.empty()? nullptr : autre.ptr->cloner() } {
}
delegue(delegue &&) = default;
delegue &operator=(const delegue &autre) {
delegue{ autre }.swap(*this);
return *this;
}
delegue &operator=(delegue &&) = default;
~delegue() = default;
// invocation
R operator()(A arg) {
assert(!empty());
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;
}
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;
}