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 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.

  1. In its fullest expression, type erasure is the process of turning a wide variety of types with a common interface into one type with that same interface.