Les constats superflus sont mauvais du point de vue de l'API:
Mettre des constants superflus supplémentaires dans votre code pour les paramètres de type intrinsèque transmis par la valeur encombre votre API sans faire de promesse significative à l'appelant ou à l'utilisateur de l'API (cela ne fait que gêner la mise en œuvre).
Trop de «const» dans une API quand ils ne sont pas nécessaires, c'est comme « pleurer le loup », finalement les gens commenceront à ignorer «const» parce qu'il est partout et ne veut rien dire la plupart du temps.
L'argument "reductio ad absurdum" pour les consts supplémentaires dans l'API est bon pour ces deux premiers points serait que si plus de paramètres const sont bons, alors chaque argument qui peut avoir un const dessus, DEVRAIT avoir un const dessus. En fait, si c'était vraiment bon, vous voudriez que const soit la valeur par défaut pour les paramètres et ayez un mot-clé comme "mutable" uniquement lorsque vous voulez changer le paramètre.
Essayons donc de mettre const partout où nous pouvons:
void mungerum(char * buffer, const char * mask, int count);
void mungerum(char * const buffer, const char * const mask, const int count);
Considérez la ligne de code ci-dessus. Non seulement la déclaration est plus encombrée et plus longue et plus difficile à lire, mais trois des quatre mots clés «const» peuvent être ignorés en toute sécurité par l'utilisateur de l'API. Cependant, l'utilisation supplémentaire de «const» a rendu la deuxième ligne potentiellement DANGEREUSE!
Pourquoi?
Une mauvaise lecture rapide du premier paramètre char * const buffer
pourrait vous faire penser qu'il ne modifiera pas la mémoire dans le tampon de données qui est passé - cependant, ce n'est pas vrai!Un «const» superflu peut conduire à des hypothèses dangereuses et incorrectes sur votre API lorsqu'il est analysé ou mal lu rapidement.
Les constats superflus sont également mauvais du point de vue de l'implémentation du code:
#if FLEXIBLE_IMPLEMENTATION
#define SUPERFLUOUS_CONST
#else
#define SUPERFLUOUS_CONST const
#endif
void bytecopy(char * SUPERFLUOUS_CONST dest,
const char *source, SUPERFLUOUS_CONST int count);
Si FLEXIBLE_IMPLEMENTATION n'est pas vrai, alors l'API «promet» de ne pas implémenter la fonction de la première manière ci-dessous.
void bytecopy(char * SUPERFLUOUS_CONST dest,
const char *source, SUPERFLUOUS_CONST int count)
{
// Will break if !FLEXIBLE_IMPLEMENTATION
while(count--)
{
*dest++=*source++;
}
}
void bytecopy(char * SUPERFLUOUS_CONST dest,
const char *source, SUPERFLUOUS_CONST int count)
{
for(int i=0;i<count;i++)
{
dest[i]=source[i];
}
}
C'est une promesse très stupide à faire. Pourquoi devriez-vous faire une promesse qui n'apporte aucun avantage à votre appelant et limite seulement votre mise en œuvre?
Les deux sont des implémentations parfaitement valides de la même fonction, donc tout ce que vous avez fait est lié inutilement une main derrière votre dos.
De plus, c'est une promesse très superficielle qui est facilement (et juridiquement contournable).
inline void bytecopyWrapped(char * dest,
const char *source, int count)
{
while(count--)
{
*dest++=*source++;
}
}
void bytecopy(char * SUPERFLUOUS_CONST dest,
const char *source,SUPERFLUOUS_CONST int count)
{
bytecopyWrapped(dest, source, count);
}
Écoutez, je l'ai implémenté de cette façon de toute façon, même si j'ai promis de ne pas le faire - en utilisant simplement une fonction wrapper. C'est comme quand le méchant promet de ne pas tuer quelqu'un dans un film et ordonne à son homme de main de le tuer à la place.
Ces constantes superflues ne valent rien de plus qu'une promesse d'un méchant de film.
Mais la capacité de mentir devient encore pire:
J'ai été informé que vous pouvez ne pas faire correspondre const dans l'en-tête (déclaration) et le code (définition) en utilisant un faux const. Les défenseurs const-happy affirment que c'est une bonne chose car cela vous permet de mettre const uniquement dans la définition.
// Example of const only in definition, not declaration
class foo { void test(int *pi); };
void foo::test(int * const pi) { }
Cependant, l'inverse est vrai ... vous ne pouvez mettre une constante parasite que dans la déclaration et l'ignorer dans la définition. Cela ne fait que constuire superflu dans une API plus une chose terrible et un mensonge horrible - voir cet exemple:
class foo
{
void test(int * const pi);
};
void foo::test(int *pi) // Look, the const in the definition is so superfluous I can ignore it here
{
pi++; // I promised in my definition I wouldn't modify this
}
En fait, tout ce qui est superflu est de rendre le code de l'implémentateur moins lisible en l'obligeant à utiliser une autre copie locale ou une fonction d'encapsuleur quand il veut changer la variable ou passer la variable par référence non const.
Regardez cet exemple. Quel est le plus lisible? Est-il évident que la seule raison de la variable supplémentaire dans la deuxième fonction est que certains concepteurs d'API ont ajouté une constante superflue?
struct llist
{
llist * next;
};
void walkllist(llist *plist)
{
llist *pnext;
while(plist)
{
pnext=plist->next;
walk(plist);
plist=pnext; // This line wouldn't compile if plist was const
}
}
void walkllist(llist * SUPERFLUOUS_CONST plist)
{
llist * pnotconst=plist;
llist *pnext;
while(pnotconst)
{
pnext=pnotconst->next;
walk(pnotconst);
pnotconst=pnext;
}
}
J'espère que nous avons appris quelque chose ici. La constance superflue est une horreur encombrante, un bourrin ennuyeux, une promesse superficielle et dénuée de sens, un obstacle inutile, et conduit parfois à des erreurs très dangereuses.