Constance, pureté, et légèreté du code

Quelques réflexions suscitées par la lecture d’une entrée de blog par John Carmack (oui, M. Wolfenstein 3D “Doom sur Wikipedia], Quake… himself) sur un blog de développeurs de jeux vidéos.

De la pureté de l’être (et surtout des fonctions)

Donc, John Carmack fait une introduction (simple et sans jargon) sur un concept d’ingénierie logicielle à la mode, à qui l’on doit le retour en grâce de Lisp et l’apparition de langages comme Haskell : les fonctions pures. Une fonction pure est une fonction qui renvoie toujours la même chose pour un jeu de paramètres donné. Pour les jeux vidéos (mais plus globalement pour les logiciels un peu complexes), elles ne peuvent donc pas modifier une variable d’état globale ou donner des résultats différents si appelées depuis des trends différents sur les mêmes entrées. Elles amènent donc beaucoup de facilités pour les tests unitaires, le multithreading, l’utilisation de versions différentes de la même fonction…

John Carmack parle à un public de développeurs de jeux vidéos, donc majoritairement des programmeurs C++, pour tout un tas de bonnes ou mauvaises raisons industrielles, pratiques, de performances, d’historique… Il prédit l’apparition d’un mot clé pure dans les prochaines versions de la norme pour aider le compilateur à détecter et faire respecter les contrats de pureté, C++ n’ayant pas de mécanismes intrinsèques pour imposer la pureté d’une fonction. Il fait le parallèle avec le mot clé const, qui va donner des informations supplémentaires au compilateur, qui pourra ensuite vérifier qu’un objet n’est pas modifié1, d’autant plus pertinent qu’une fonction membre const a de bonnes chances d’être pure.

De l’inconstance des membres

Le lien avec OpenCV ? J’y arrive. La classe cv::Mat définit des fonctions membres permettant de créer un nouvel objet à partir d’un sous-ensemble d’une matrice, par exemple cv::Mat::row(int i) et cv::Mat::col(int j) si l’on souhaite créer une nouvelle matrice à partir d’une ligne ou d’une colonne. Ces fonctions membres sont déclarées const, car elles ne modifient pas l’objet d’origine.

Or, et c’est là qu’est la subtilité, la nouvelle matrice ainsi obtenue est produite sans copie de données, c’est-à-dire qu’il y a uniquement création d’un en-tête qui permet d’accéder aux données de la matrice originale. Comme cette nouvelle matrice n’est pas, elle, déclarée constante, on peut la modifier. Le code suivant est donc tout à fait licite (on modifie une nouvelle référence sur l’objet d’origine) :

    cv::Mat bigMat = cv::Mat::ones(1024, 1024, CV_32FC1);
    cv::Mat row = bigMat.row(123);%%%
    row *= cv::Scalar(2);

alors que ceci est interdit:

    bigMat.row(123) *= cv::Scalar(2);%%%

(car on essaie de modifier l’objet d’origine).

La moral de cette histoire ?

Constance n’est donc pas pureté, en tout cas pas avec OpenCV. Et de plus, constance et inconstance peuvent, même sur un exemple simple, aller de paire. Si le système de gestion automatique de la mémoire pour les matrices est très pratique, il rend délicate l’écriture de fonctions pures reposant sur OpenCV, et oblige à bien vérifier ce que l’on fait dans l’écriture de code exploitant le multithreading pour modifier une matrice.

Edit du 7 mai 2012

Comme indiqué implicitement dans la réponse à une question sur StackOverflow, OpenCV 2.4 a supprimé le mot-clé const dans la fonction cv::Mat(cv::Rect()). On peut donc passer le résultat de cette fonction comme argument de cv::Mat::copyTo(). Les fonctions permettant de créer une matrice à partir d’une ligne ou d’une colonne sont par contre toujours marquées comme constantes.


  1. Et brailler très fort dans le cas contraire. ^

Related