Les dernières heures du moc ?
Deux solutions permettent de se passer d'une étape de précompilation

Le , par dourouc05, Responsable Qt
Qt est un framework C++ relativement ancien. Pour supporter des fonctionnalités intéressantes (gestion des signaux et slots, métaobjets, introspection, etc.) sur un grand nombre de compilateurs pas forcément très au courant des standards C++, il a dû développer un outil de génération de code, le moc (metaobject compiler).
Il donc n’est pas suffisant de compiler les fichiers source d’une application et de les lier avec le framework, puisqu’il faut générer du code. Ce simple oubli a déjà causé bien des pertes de temps, même pour des développeurs plus chevronnés. Les outils de compilation s’y sont habitués (QMake, CMake et d’autres), les EDI également… mais pas forcément les utilisateurs : il est la cause d’un certain nombre de grandes incompréhensions de la part de débutants avec le framework. Les plus puristes n’apprécient pas non plus cette partie hors standard de la chaîne de compilation.
Plus techniquement, moc est un outil en ligne de commande qui prend en entrée un fichier source de l’application avec des macros particulières (la plus connue étant Q_OBJECT) et génère un autre fichier source à ajouter à la compilation. Ce dernier implémente les métaobjets grâce aux informations récoltées par ces macros particulières. Pour cette étape de génération, moc utilise un analyseur assez naïf qui se limite à l’extraction des informations nécessaires à son bon fonctionnement, il achoppe donc sur du code C++ plus avancé ou sur les nouveautés de C++11/14.
Clang
Une première expérience était de remplacer l’étape du moc externe au compilateur par une extension de ce compilateur, afin de générer le code nécessaire à la volée. moc-ng s’occupe de cette partie, avec un plug-in pour Clang (un compilateur C++), mais aussi un outil en ligne de commande équivalent au moc mais utilisant Clang pour l’analyse du code source de l’application (et évite donc les écueils de la solution actuelle).
Clang est bien connu pour ses messages d’erreur, bien plus explicites que ceux d’autres compilateurs. Proposer le moc en tant qu’extension permet de générer des messages d’erreur en cas de mauvaise utilisation des commandes du générateur, notamment en cas de typo. Le message généré par le moc actuel est très suboptimal :

Grâce aux fonctionnalités plus avancées de Clang pour la recherche dans les fichiers source, l’extension propose même la correction de la typo à l’origine de l’erreur :

Bien qu’il ne s’agisse que d’une expérience, d’une preuve de faisabilité, cette réimplémentation fonctionne bien : elle peut remplacer le moc pour la compilation d’une grande partie de Qt, les tests n’échouant pas plus souvent qu’avec le moc officiel.
Se passer du moc
Boost implémente déjà les concepts de signaux et slots sans utiliser de précompilateur dans Boost.Signals2. Le moc propose également des fonctionnalités d’introspection et de réflexion, c’est à ce moment que le comité de standardisation du C++ entre en jeu : le document N3814 est une demande de propositions pour la réflexion à la compilation. Cette fonctionnalité sera intégrée au plus tôt dans C++17 et aucune version n’est actuellement implémentée dans un compilateur.
La proposition N3951 donne de nouvelles significations aux mots clés typedef et typename, de telle sorte que le code
Code : Sélectionner tout
1
2
3
4
5
6
7
8
class SomeClass {  
public: 
  int foo(); 
  void bar(int x); 
}; 
 
vector names = { typename... }; 
auto members = std::make_tuple(typedef...);
deviendrait équivalent à
Code : Sélectionner tout
1
2
vector names =  { "SomeClass",  "foo", "bar" }; 
auto members = std::make_tuple(static_cast(nullptr), &SomeClass::foo, &SomeClass::bar);
Ainsi, un compilateur implémentant cette fonctionnalité pourrait générer l’équivalent du QMetaObject actuel à l’aide de quelques traits, sans outil extérieur. Pour le moment, il est possible d’expérimenter cette fonctionnalité en réécrivant soi-même les typedef… et typename… dans le prototype, avec le grand avantage de garder la compatibilité binaire avec le code généré par le moc : les structures générées donnent tous les slots, les signaux et les propriétés requises pour la construction du métaobjet à l’exécution. Pour distinguer les signaux et slots, C++11 propose des attributs, mais il serait plus agréable de les placer au niveau des spécifications d’accès de groupe (soit une extension du document N3955) :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
class MyObject : public QObject { 
    Q_OBJECT 
public [[qt::slot]]: 
    void fooBar(); 
    void otherSlot(int); 
public [[qt::signal]]: 
    void mySignal(int param); 
public: 
   enum [[qt::enum]] Foobar { Value1, Value2  }; 
};
Sources : Proof Of Concept: Re-implementing Qt moc using libclang et Can Qt’s moc be replaced by C++ reflection?
Voir aussi :


Billet original


Vous avez aimé cette actualité ? Alors partagez-la avec vos amis en cliquant sur les boutons ci-dessous :


 Poster une réponse

Avatar de germinolegrand germinolegrand - Membre expert https://www.developpez.com
le 12/03/2014 à 14:21
Le travail de ce SG est en pleine ébullition en ce moment ^^ Je lisais justement des commentaires au sujet du N3955 ce matin, je te remercie de m'éclairer en le remettant dans son contexte .
Avatar de chrtophe chrtophe - Rédacteur/Modérateur https://www.developpez.com
le 12/03/2014 à 19:24
J'ai rien compris, mais je ne suis qu'un amateur en programmation. L’introspection et la réflexion ne me parle pas.

le système du moc ne m'a jamais posé de prob. une fois compris la notion de signaux et slots.

je compile via qmake puis make et j'ai jamais eu de prob.

En regardant ce que signifie la reflexion/introspection dans wikipedia.

"l'introspection, qui est la capacité d'un programme à examiner son propre état ;"

Le fait d’accéder à une propriété, c'est examiner un état non ? donc là je vois pas.

D'après ce que je pense avoir réussi à comprendre, il s'agit d'utiliser les nouvelles fonctionnalités intégrés dans les nouvelles normes C++ ?

je suis encore sur Qt 4.x, j'ai pas fini de me former que j'ai déjà du retard, et que je vais en prendre encore plus.

Concrètement qu'est ce qui va changer, et quel est l'interet par rapport à la situation actuelle, car pour moi le moc est complètement transparent.
Avatar de Kaluza Kaluza - Membre habitué https://www.developpez.com
le 12/03/2014 à 20:36
@chrtophe Je ne suis pas non plus expert de la réflexion même si j'essaye de suivre de près les discussions du comité autour de ce sujet. La réflexion permettra de générer du code à la compilation de façon encore plus générique que ce qu'il est possible de faire en métaprogrammation. La réflexion comprend deux parties: l'introspection et la réification. L'introspection consiste à pouvoir extraire des infos sur les types: ça peut être vu comme des type_traits mais en mieux pour par exemple récupérer le nom d'une fonction ou d'un membre d'une classe. La réification consiste à convertir certains éléments (par exemple de chaînes de caractères) en éléments de grammaire du langage. Si je schématise, si tu imagines que tu as une classe qui s'appelle Toto alors l'introspection permettra de récupérer "Toto" en tant que chaîne de caractère et la réification te permettra de retransformer cette chaîne de caractère en nom de classe. C'est extrêmement simplifié, mais c'est comme ça que je le comprends. Pour les gens qui codent des bibliothèques, la réflexion changera du tout au tout la façon de programmer parce que l'on se mettra davantage à écrire des générateurs de code que le code lui-même... En mélangeant la métaprogrammation par template, les concepts et la réflexion, désigner des bibliothèques en C++ va vraiment devenir génial (bon c'était déjà le cas, mais ce sera encore mieux). Et puis en allant plus loin que ça, c'est la création d'EDSL qui deviendra un vrai plaisir (boost::proto c'est bien, mais ça ne s’adresse quand même pas au premier venu...).
N'hésitez pas à me corriger si jamais ce message était à côté de la plaque...
Avatar de bretus bretus - Membre éprouvé https://www.developpez.com
le 13/03/2014 à 8:41
Bonjour,

Introspection = Pouvoir récupérer dynamiquement des informations sur les instances (nom de la classe, liste des attributs, etc.)

Réflexion = Pouvoir récupérer des attributs par leur nom (chaîne de caractères, etc.), appeler des fonctions par leur nom, pouvoir construire des objets à partir d'un nom de classe, etc.

A quoi ça sert :
- Le mécanisme de signaux/slot repose sur la réflexion. Lire un maclasse_moc.cpp est assez instructif en la matière, on voit comment Qt redirige un appel à "monSlot" sur la méthode effective.
- Ça permet d'appeler les slot et propriété dans QtScript sans se frapper un binding pour chaque classe C++. Sans génération de code, on devrait écrire des choses de ce type binding python avec boost (bêtement oubliée par un compilateur standard qui pourrait les cracher par exemple en XML)
- Dans les dé-sérialisations (chaîne de caractères => instance C++), on doit créer une instance à partir d'un nom de classe. La réflexion est bien pratique pour appeler un constructeur.
- ...
Avatar de chrtophe chrtophe - Rédacteur/Modérateur https://www.developpez.com
le 13/03/2014 à 10:29
Merci pour l'info les gars.

Si j'ai bien compris, cela permet de sérialiser des classes en chaines de caractères par exemple.
Offres d'emploi IT
Ingénieur H/F
Safran - Ile de France - Moissy-Cramayel (77550)
Ingénieur développement fpga (traitement vidéo) H/F
Safran - Ile de France - 100 rue de Paris 91300 MASSY
Architecte et intégrateur scade/simulink H/F
Safran - Ile de France - Vélizy-Villacoublay (78140)

Voir plus d'offres Voir la carte des offres IT
Responsable bénévole de la rubrique Qt : Thibaut Cuvelier -