Type erasures: introduction
Les librairies boost::any, boost::function et adobe::poly sont autant de variations d'une technique très intéressante ayant fait son apparition dans l'univers du C++ au début des années 2000. La technique porte le nom de type erasure.
Dans le neuvième chapitre de leur ouvrage C++ Template Metaprogramming, David Abrahams et Aleksey Gurtovoy décrivent la technique comme étant une procédure permettant de convertir une large variété de types partageant une interface commune en un type unique implantant cette interface[1].
Implantation
En substance, la technique s'articule autour de deux classes: Concept et Model.
- La classe virtuelle Concept représente l'interface commune. Dans l'exemple ci-dessous, l'interface commune se résumerait à la méthode print.
- La classe concrète Model implante l'interface définie par la classe Concept. Selon le cas, il peut s'agir d'un simple wrapper autour d'un objet implantant lui-même l'interface du Concept ou encore d'un adapter autour d'un objet dont l'interface varie légèrement de celle définie par la classe Concept, mais dont la sémantique est identique.
La classe TypeErasure implante l'interface du Concept en lui transférant directement les appels. Le prix à payer pour utiliser cette technique se résume donc essentiellement à un appel virtuel.
Un constructeur de copie, un opérateur d'assignation ainsi qu'un opérateur de swap sont fournis afin de conserver la sémantique de valeur des objets stockés dans un TypeErasure.
class TypeErasure {
private:
struct Concept {
virtual ~Concept() {}
virtual Concept* copy() const = 0;
virtual void print() const = 0;
};
template <typename T>
struct Model: Concept {
explicit Model(const T& x) :
data(x) {
}
Concept* copy() const {
return new Model(data);
}
void print() const {
data.print();
}
T data;
};
Concept* impl;
public:
template <typename T>
explicit TypeErasure(const T& x) :
impl(new Model<T> (x)) {
}
~TypeErasure() {
delete impl;
}
TypeErasure(const TypeErasure& x) :
impl(x.impl->copy()) {
}
TypeErasure& operator=(TypeErasure x) {
swap(x);
return *this;
}
void swap(TypeErasure& other) {
using std::swap;
swap(impl, other.impl);
}
void print() const {
impl->print();
}
};
Une spécialisation de l'opérateur swap est fournie pour la classe TypeErasure afin de maximiser les performances lorsque la classe est utilisée avec les algorithmes et conteneurs de la STL.
namespace std {
template<>
void swap(TypeErasure& a, TypeErasure& b) {
a.swap(b);
}
}
Considérant les trois classes ci-dessous dont les interfaces sont identiques, il est possible, grâce à la technique des type erasure, de stocker des instances de ces dernières dans une collection unique et de manipuler celles-ci de manière uniforme — leur type ayant effectivement été effacé.
class RandomObjectA {
public:
void print() const {
std::cout << "RandomObjectA" << std::endl;
}
};
class RandomObjectB {
public:
void print() const {
std::cout << "RandomObjectB" << std::endl;
}
};
class RandomObjectC {
public:
void print() const {
std::cout << "RandomObjectC" << std::endl;
}
};
int main() {
std::vector<TypeErasure> doc;
doc.push_back(TypeErasure(RandomObjectA()));
doc.push_back(TypeErasure(RandomObjectB()));
doc.push_back(TypeErasure(RandomObjectC()));
std::reverse(doc.begin(), doc.end());
std::for_each(doc.begin(), doc.end(), [](const TypeErasure& e) {
e.print();
});
return 0;
}
Le code source complet de cet exemple peut être consulté en ligne.
Références
Une des premières traces de la technique remonte à ma connaissance à un article intitulé Valued Conversions écrit par Kevlin Henney, l'homme derrière boost::any, et paru dans le numéro de juillet/août 2000 du C++ Report.
La technique a également fait une apparition, sans toutefois être nommée comme telle, dans une présentation d'Eric Berdah, de chez Adobe Labs, intitulée Classes that Work. Un enregistrement de cette présentation est disponible sur le wiki du Software Technology Lab d'Adobe. Une version légèrement modifiée de l'exemple présenté par Berdah peut être consultée en ligne.
La section 9.7 du livre de David Abrahams et Aleksey Gurtovoy, C++ Template Metaprogramming, présente également la technique en montrant comment cette dernière peut être utilisée pour wrapper des functors.
Finalement, la technique a fait l'objet d'une présentation complète lors du BoostCon 2010. Les diapositives de Nevin Liber et un enregistrement de la présentation sont disponibles à partir du wiki du BoostCon 2010.