La suppression d’export

Bonjour à tous et bienvenue dans cet article qui est dédié à une ancienne fonctionnalité de C++ : export. Je vous propose de voir ensemble à quoi elle servait (avant C++11) ainsi que les raisons qui ont poussé à sa suppression.

export est un cas assez particulier de l’histoire du C++ puisqu’il s’agit d’une des seules fonctionnalités majeures du langage a avoir été largement laissée de côté par les implémentations.

Je vous propose donc de commencer par rappeler à quoi servait export puis de voir les raisons qui ont poussé à sa suppression du standard, en particulier après les retours d’Edison Design Group (EDG), les seuls à l’avoir implémenté.

Export, ça sert à quoi ?

Beaucoup de débutants C++ qui développent leur première classe template ont été confrontés au problème suivant :

Ici, l’éditeur des liens va lever une erreur. En effet, le programme comporte deux unités de traduction : celle de main.cpp, dans laquelle la définition du constructeur Foo<T> n’est pas disponible et celle de Foo.cpp, dans laquelle aucune instanciation de Foo<int> n’a jamais lieu. Le résultat est que le constructeur Foo<int>::Foo n’existe pas :

Ce problème est intrinsèquement lié au fonctionnement des templates. Notez bien la distinction entre la déclaration de Foo , la définition des fonctions de Foo et instanciation de Foo<int>.

Dans l’instanciation de Foo<int> dans la fonction main, le compilateur n’a besoin que de la déclaration du constructeur. En effet, il peut simplement appeler cette fonction même si elle définie dans une autre unité de traduction.

Ce faisant, le compilateur se contente de récupérer la déclaration de Foo<int>::Foo, qui se trouve dans l’entête Foo.h. Il sait que le constructeur ne prenant aucun argument existe et que le code est donc valide, ce sera le travail de l’éditeur des liens de lier cet appel à la bonne fonction. Malheureusement, l’éditeur des liens ne trouve pas cette fonction puisque Foo<int> n’est pas instancié dans l’unité de traduction où Foo<T>::Foo() est définie (Foo.cpp).

La solution immédiate est donc de forcer l’instanciation de Foo<int> (ou juste du constructeur) dans Foo.cpp :

Et voilà, tout marche bien ! Le constructeur est maintenant défini dans l’unité de traduction de Foo.cpp grâce à l’instanciation explicite.

Mais cette méthode pose un gros souci de couplage. Si l’utilisateur veut Foo<double>, il faut instancier aussi Foo<double> dans Foo.cpp, etc… Ce n’est pas vraiment acceptable.

La solution la plus classique est d’inclure simplement Foo.cpp dans Foo.h, de manière à ce qu’en fait, la déclaration et la définition soient disponibles aux fichiers qui incluent Foo.h :

Remarquez qu’ici, j’ai renommé Foo.cpp en Foo.tpp. C’est quelque-chose d’assez courant puisque ici, on ne souhaite pas compiler Foo.cpp. Utiliser .tpp permet de clairement montrer qu’il ne s’agit ici que d’un moyen de séparer les déclarations des définitions d’un point de vue gestion de fichiers, même si en réalité Foo.tpp fait partie de Foo.h. Ça permet aussi d’éviter des mauvaises surprises lorsqu’on compile tous les fichiers C++ d’un dossier (avec  g++ *.cpp par exemple).

Cette solution permet effectivement de séparer la déclaration de la définition en deux fichiers distincts, mais en termes de compilation, c’est strictement équivalent au cas où la définition est inclue dans l’entête.

C’est là qu’intervient export. Ce mot clef permet(ait) de  déclarer que le template est défini dans une autre unité de traduction et donc que l’instanciation doit se faire au moment de l’édition des liens :

Ici, tout marche bien sans aucun problème… ou presque… n’essayez pas de compiler ça avec GCC, MSVC, ICC ou encore Clang, ils ne savent pas faire !

Pourquoi export a été retiré ?

C’est une bonne question dites donc ! Pourquoi export a-t-il été retiré du langage avec C++11 ? Et bien les raisons sont nombreuses et c’est d’ailleurs pour ça que j’ai eu envie de faire cet article parce que le cas d' export est vraiment particulier.

Herb Sutter avait fait deux articles (ici et ), sur  export et les problèmes associés à son implémentation et son utilisation. Je vous propose aujourd’hui de nous concentrer sur la proposition n1426 “We can’t affort export” qui a mis fin à la vie de ce pauvre mot clef.

Parmi les justifications de la demande du retrait d' export, la n1426 indique que le standard ne prenait pas en compte certains cas particuliers que je ne vais pas détailler. Daveed Vandevoorde, de l’Edison Design Group (EDG) indique :

I spent more than six months just on ODR checking, while discovering numerous ODRrelated holes in the standard in the presence of export. Before export, ODR violations didn’t have to be diagnosed by the compiler. Now it’s necessary because you need to combine internal data structures from different translation units, and you can’t combine them if they’re actually representing different things, so you need to do the checking

export n’a été implémenté que par EDG, qui sont connus comme étant parmi les meilleurs implémenteurs de front end C++ au monde. Le front end correspond généralement à l’analyse syntaxique et lexicale visant à produire une représentation intermédiaire utilisable par l’optimiseur et le back end.

Le front end d’EDG est utilisé par plusieurs compilateurs tels que Intel C++ Compiler, Micrsoft Visual C++, Comeau C++ Compiler … Parmi ces utilisateurs, le papier indique que seul le compilateur Comeau propose  export  (ainsi que l’implémentation de bibliothèque standard Dinkum C++ et la suite d’outils d’analyse Plum Hall qui le supportaient également).

Les seuls véritables retours de l’implémentation d' export  viennent donc d’EDG, qui proposent des chiffres assez incroyables quant au temps de développement. Ainsi, le seul design de la feature aura coûté 1,5 personne/année et plus de 3 personnes/année pour l’implémentation. A titre de comparaison, l’implémentation de la totalité du langage Java depuis zéro avait prit 2 p/a à la même équipe.

Les retours d’EDG montrent que dans la pratique, export a tendance à proposer des temps de compilation plus long, et EDG estime qu’il ne s’agit pas d’un problème spécifique à leur implémentation.

Le papier rappelle d’ailleurs que beaucoup de développeurs ont une mauvaise appréhension de cette fonctionnalité et peuvent s’attendre à pouvoir cacher l’implémentation de leur templates, ce qui n’est pas le cas. En effet, le compilateur doit toujours avoir accès aux templates, ils ne sont donc pas précompilés en code objet mais au mieux vers une structure de données approchant un AST utilisable par le compilateur. Il ne s’agit donc en aucun cas d’une obfuscation comparable au processus de compilation.

Enfin, il est mentionné que le risque de frein à la standardisation qu’implique export, puisqu’il entraîne beaucoup de complications (l’introduction des concepts en présence d' export serait particulièrement complexe par exemple, surtout maintenant que les retours d’EDG ont dévoilé des failles dues à  export dans le standard). Le laisser impliquerait d’ailleurs que les autres implémentations devraient sacrifier quelques cycles de release pour développer export (comme l’a fait EDG), temps qu’ils pourraient passer sur les fonctionnalités à venir des prochaines versions du langage.

La n1426 discute aussi du fait que la suppression d' export pourrait entraîner un précédent faisant qu’une fonctionnalité qui n’a pas été unanimement adoptée par les compilateurs puisse être retirée du standard. Elle indique pour cela que le cas d' export est très particulier, notamment en raison de la complexification du processus de compilation et de l’utilité très relative du mot clef.

A la suite de cette proposition, et après pas mal de discussions, export a donc été retiré du langage avec C++11. Le mot clef est dorénavant réservé mais inutilisé, et est d’ailleurs le seul dans ce cas à l’heure actuelle.

Avant de vous quitter, je vous propose les autres citations d’EDG concernant leur expérience avec l’implémentation d' export :

  • Steve Adamczyk: “Although we oppose the language feature, we worked hard to produce the best possible implementation we could; we believe this is a high-quality implementation.”
  • John Spicer : “Export was more work to implement than any three other C++ language features combined (e.g., namespaces, member templates).”
  • Steve Adamczyk : “For the first time in our history, EDG started slipping promised dates, both on export and on other features because of export.”
  • Daveed Vandevoorde [lorsqu’on insiste sur le fait qu’export ne peut pas être si complexe]: “I used to support export too. I believe strongly that people can’t comment on implementing export unless they’ve done it.”
  • Tous [quand on leur demande des conseils pour implémenter export]: “Don’t.”

Voilà donc pour cet article, n’hésitez pas à consulter directement la proposition si vous souhaitez plus de détails. Et à la prochaine !

Références et ressources

 

%d bloggers like this: