IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

Divagations philosophiques sur la const-correctness


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 493
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 493
    Billets dans le blog
    1
    Par défaut Divagations philosophiques sur la const-correctness
    Salut les amis

    Dans notre projet sur micro-contrôleur, on a fait des classes pour wrapper une bibliothèque de drivers écrite en C. Il arrive que ces classes ne possède pas de données membres (par exemple, quand on wrappe un module ADC et qu'il n'y en a qu'un seul ADC sur le micro) et souvent la donnée membre se résume à un pointeur (généralement caché par un typedef de la bibliothèque C).

    Voici par exemple ce que donne une classe pour la gestion des bus I2C :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include "bibliotheque_drivers.h"
     
    class I2C {
    public:
    	// Creation a partir d'un handle C (normalement une adresse vers un peripherique "memory-mapped")
    	explicit I2C(I2C_HandleTypeDef* I2Cx);
     
    	// Non copiable
    	I2C(const I2C&) = delete;
    	I2C& operator=(const I2C&) = delete;
     
    	// Operations de lecture / ecriture qui deleguent a des fonctions C qui prennent le handle C en parametre
    	bool readFrom(std::uint8_t dest, std::uint8_t* data, std::size_t length);
    	bool writeTo(std::uint8_t dest, const std::uint8_t* data, std::size_t length);
     
    private:
    	I2C_HandleTypeDef* I2Cx_m;
    };
    Ici, les fonctions read() et write() ne sont pas marquées comme const mais bien sûr elle ne change pas le handle C. Il sera le même tout au long de la vie d'un objet I2C.

    D'un point de vue const-correctness pur, je pourrais ajouter const et mettre un mutable si besoin (je crois en fait que ça compile sans pour cette classe-ci...).

    D'un point de vue plus général, c'est quand même étonnant de dire qu'une fonction qui s'appelle write(), qui va écrire des données et donc d'une certaine manière "modifier" le bus physique, est const.

    Est-ce que la const-correctness ne devrait se juger que d'un point de vue compilation (je modifie un champ, je ne peux pas être const ; je ne modifie aucun champ, je peux l'être) ou est ce que ça s'applique aussi d'un point de vue action de la classe (je modifie une base de données mais mon handle vers la base est inchangée, alors je ne suis pas vraiment const) ? Qu'en pensez-vous ?

  2. #2
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    Salut,

    Il y a deux cas possibles :
    • Si I2C possède le I2C_HandleTypeDef encapsulé, alors il faut propager la const-correctness : les fonctions qui ne modifient pas le I2C_HandleTypeDef doivent être const. Conceptuellement, le pointeur pourrait être remplacé par std::experimental::propagate_const.
    • Si I2C ne possède pas le I2C_HandleTypeDef encapsulé alors, pour faire de la const-correctness, il faudrait découper I2C en deux classes : une qui n'a que les fonctions de lecture et une qui a les fonctions de lecture et écriture, un peu comme std::vector::const_iterator et std::vector::iterator. Dans ce cas, il n'est pas gênant que I2C soit copiable : la copie en question ne ferait alors qu'une copie de pointeur.


    Remarque dans un cas différent de ton exemple :
    Dans le cas particulier d'un arbre dont les nœuds possèdent un pointeur vers le nœud parent, je propage aussi la const-correctness quand on accède à un nœud parent depuis un nœud enfant même si, à proprement parler, les nœuds enfants ne possèdent pas le nœud parent. Autrement, en faisant myNode.getParentOrNull()->getChild(myNodeIndex), on pourrait faire sauter la const-correctness.

  3. #3
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 153
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 153
    Billets dans le blog
    4
    Par défaut
    S'il s'agit d'un pur pointeur C, sans fonctions membres, tu ne feras que le passer en paramètre et le const va s'appliquer au pointeur membre, mais pas à la donnée pointée. Donc le const ici signifierait que le pointeur ne change pas, pointe sur la même chose suite à l'opération.
    Dans ce cas, je ne mettrais pas de const non plus, le pointeur est un détail d'implémentation.
    - un write const, c'est étrange
    - le read const, seulement s'il lit depuis un buffer; si c'est streamé, la lecture entraîne une modification de l'état interne et ne devrait donc pas être const
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  4. #4
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    J'ai été "récemment" confronté à un choix un peu similaire, lors de l'implémentation d'un système de signaux et de slot (en C++11 et ultérieur ), car le "propriétaire" du signal pouvait être déclaré comme constant.

    En gros, ma cogitation m'avait amené à me poser une question du genre idiote: il se peut que je veuille faire appel à mon signal depuis une fonction membre constante du propriétaire du signal, mais, pourquoi ne pourrais-je pas souhaiter "bloquer" le signal pour éviter qu'il ne soit émis "à répétition" alors qu'un seul et unique appel pourrait être suffisant

    La réponse que j'ai trouvée a été que, finalement, le blocage (ou son déblocage) du signal n'influait en rien dans l'état du propriétaire, et que c'étaient deux choses absolument transversales : le signal a "sa propre vie" à l'intérieur de son propriétaire, totalement indépendante de toute notion de constance qui pourrait être appliquée à ce propriétaire.

    Si bien que, assez bizarrement, le signal expose des fonctions comme block et unblock (qui permettent de régir l'émission du signal) qui sont des fonctions constantes. Le tout m'étant autorisé à grand coups d'utilisation de mutable.

    Notes au passage que la notion de connexion, qui prend une importance primordiale dans mon implémentation (car elle permet de maintenir à jour de manière automatique la liste des slots connectés à un signal donné) présente exactement la même caractéristique .

    (je fournirai le code de cette implémentation si quelqu'un le demande )

    Au final, je crois que la "bonne pratique" consiste, effectivement, à considérer que ton handle correspond d'une certaine manière à un cache de ta classe: ce n'est pas parce que tu met le cache de ta classe à jour que tu en viens à modifier effectivement l'état de ta classe.

    Comme tes fonctions readFrom et writeTo ne font en définitive qu'agir au niveau de ce cache, il est "cohérent" que tu puisse y faire appel depuis une instance constante de ta classe, et il est donc "cohérent" de les déclarer en tant que fonctions constantes.

    Bien sur, cela implique que ton handle sera sans doute déclaré mutable, de manière à te permettre de faire appel aux fonctions qui vont bien dans ces deux fonctions membres . Mais c'est quelque chose qui ne me choque absolument pas, ne serait-ce que parce que tu ne modifie effectivement pas l'état de ton wrapper : même si ces deux fonctions venaient à renvoyer false, ce qui impliquerait l'échec de la tentative de lecture / écriture, ton wrapper s'arrangerait -- d'une manière ou d'une autre -- pour se remettre dans un état qui lui permettrait d'accepter ces deux ordres, si bien que l'état de ton wrapper ne resterait -- effectivement -- inchangé. A moins que tu ne sois pas d'accord avec moi
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  5. #5
    Expert confirmé
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 599
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 62
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 599
    Par défaut
    Salut,

    L'utilisation de const doit se faire, non pas sur ce qui est codé dans la fonction, mais sur les conséquences vues de l'extérieur à l'utilisation d'objets I2C qui sont const.
    Une fonction const doit forcément être thread-safe (Comment interdire un double accès si on présuppose que les fonctions n'ont aucun effet.)
    Ici, j'imagine mal la fonction readFrom() être const. Cette fonction doit impérativement se mettre en attente de la remontée des résultats. Et même si elle est thread-safe et qu'à la sortie l'objet est inchangé, il peut se produire en internes des changements d'états qui pourrait peut-être empêcher ou contrarier l'utilisation le l'objet I2C.
    Pour la fonction writeFrom(), elle pourrait être un simple trigger qui arme la communication et rend immédiatement la main, et on pourrait considérer que l'on peut l'utiliser sans risque externe et dans cette idée pourrait être const. Si elle est bloquante, alors elle non plus ne devrait pas être const selon moi.

  6. #6
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Mais ca, l'utilisateur de ton wrapper n'en a rien à foutre!!!

    De manière générale, il va appeler une commande, et tu auras déjà du bol s'il pense à en vérifier le résultat (le booléen que tes fonctions read /write renvoient). Il y a déjà neuf chances sur dix, vu que l'utilisateur est un imbécile distrait, pour qu'il oublie de vérifier ce résultat.

    Tu ne peux donc pas demander en plus à ton utilisateur de s'inquiéter de l'état du wrapper. Car, s'il ne pense déjà pas à s'assurer que la commande qu'il a lancée s'est correctement déroulée, tu ne peux décemment pas compter sur lui pour qu'il pense à s'assurer que le wrapper est "en état" (quoi que cela puisse signifier) d'accepter la commande suivante.

    Alors, oui, bien sur! ton wrapper va se trouver dans différents états en interne, les commandes read et write vont -- très certainement -- modifier l'état du wrapper, mais ton utilisateur, lui, il n'en a rien à foutre de ces détails d'implémentation!

    Alors, oui, tu peux afficher en grand dans ta documentation un "attention, pour pouvoir faire appel à read ou à write, vous devez utiliser une référence non constante sur ce wrapper", mais ca peut le placer dans des situations vachement inconfortables, ne serait-ce que parce que cela pourrait l'empêcher de transmettre le wrapper sous une forme constante à une fonction qui n'a -- a priori -- aucune raison de le modifier uniquement parce que cette fonction en appellera une autre qui -- elle -- voudra faire appel à read ou à write.

    Si tu acceptes l'idée que ton wrapper puisse en permanence passer pour être constant, y compris lorsque l'utilisation qui en est faite modifie l'état interne de ton wrapper, tu te casses sans doute un peu la tête pour y arriver, certes, mais tu facilite énormément le travail de l'utilisateur de ton wrapper : il peut (s'il en la la possibilité) le transférer sous forme de référence constante ou non: comme toutes les fonctions sont constantes, elles seront toute accessibles sans restriction. Et ca, c'est le principal

    PS: n'oublie pas que la majeure partie du boulot d'un développeur consiste à se casser la tête de manière à ce que les autres n'aient pas besoin de le faire
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  7. #7
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 153
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 153
    Billets dans le blog
    4
    Par défaut
    Je suis d'accord si le wrapper a fonction de cache. S'il s'agit de lire un fichier ou faire du streaming, c'est plus qu'un détail d'implémentation parce que 2 lectures à la suite retourneront 2 résultats différents et masquer ça derrière du const c'est mener en erreur l'utilisateur. Donc l'interface doit refléter ceci.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  8. #8
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    Citation Envoyé par koala01 Voir le message
    De manière générale, il va appeler une commande, et tu auras déjà du bol s'il pense à en vérifier le résultat (le booléen que tes fonctions read /write renvoient). Il y a déjà neuf chances sur dix, vu que l'utilisateur est un imbécile distrait, pour qu'il oublie de vérifier ce résultat.
    Neuf chances sur dix…
    Cela dit, c'est vrai que ce serait bien que le compilateur affiche un avertissement quand on oublie de prendre en compte ce booléen. Pour cela, on peut utiliser l'attribut [[nodiscard]] du C++17.
    Sur le site godbolt.org, avec l'option -std=c++17, [[nodiscard]] fonctionne avec GCC 5.1 (et les versions supérieures) et Clang 5.0.0 (et les versions supérieures).

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Erreur sur paramètre const
    Par uriotcea dans le forum C++
    Réponses: 2
    Dernier message: 09/07/2009, 08h49
  2. probleme de "const correctness"
    Par GuiYom00 dans le forum C
    Réponses: 2
    Dernier message: 13/10/2008, 14h21
  3. question philosophique sur les champs
    Par stagolee dans le forum Modélisation
    Réponses: 3
    Dernier message: 20/08/2008, 11h14
  4. problème avec const correctness
    Par donkeyquote dans le forum C++
    Réponses: 5
    Dernier message: 12/10/2007, 01h55
  5. [VS.net 2005] Question philosophique sur les objets
    Par WriteLN dans le forum Framework .NET
    Réponses: 8
    Dernier message: 23/08/2007, 10h34

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo