Extra überflüssige Konstanten sind vom API-Standpunkt aus schlecht:
Wenn Sie in Ihren Code zusätzliche überflüssige Konstanten für vom Wert übergebene intrinsische Typparameter einfügen, wird Ihre API unübersichtlich, während dem Aufrufer oder API-Benutzer kein aussagekräftiges Versprechen gegeben wird (dies behindert nur die Implementierung).
Zu viele 'const' in einer API, wenn sie nicht benötigt werden, sind wie " weinender Wolf ". Irgendwann werden die Leute anfangen, 'const' zu ignorieren, weil es überall ist und die meiste Zeit nichts bedeutet.
Das Argument "reductio ad absurdum" für zusätzliche Konstanten in der API ist gut für diese ersten beiden Punkte. Wenn mehr Konstantenparameter gut sind, sollte jedes Argument, das eine Konstante enthalten kann, eine Konstante enthalten. Wenn es wirklich so gut wäre, möchten Sie, dass const die Standardeinstellung für Parameter ist und nur dann ein Schlüsselwort wie "veränderlich" hat, wenn Sie den Parameter ändern möchten.
Versuchen wir also, const einzufügen, wo immer wir können:
void mungerum(char * buffer, const char * mask, int count);
void mungerum(char * const buffer, const char * const mask, const int count);
Betrachten Sie die obige Codezeile. Die Deklaration ist nicht nur übersichtlicher und länger und schwerer zu lesen, sondern drei der vier 'const'-Schlüsselwörter können vom API-Benutzer sicher ignoriert werden. Die zusätzliche Verwendung von 'const' hat die zweite Zeile jedoch möglicherweise GEFÄHRLICH gemacht!
Warum?
Eine schnelle Fehlinterpretation des ersten Parameters char * const buffer
könnte den Eindruck erwecken, dass der übergebene Speicher im Datenpuffer nicht geändert wird. Dies ist jedoch nicht der Fall! Überflüssiges 'const' kann zu gefährlichen und falschen Annahmen über Ihre API führen, wenn es schnell gescannt oder falsch gelesen wird.
Überflüssige Konstanten sind auch vom Standpunkt der Code-Implementierung aus schlecht:
#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);
Wenn FLEXIBLE_IMPLEMENTATION nicht wahr ist, verspricht die API, die Funktion nicht wie folgt zu implementieren.
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];
}
}
Das ist ein sehr dummes Versprechen. Warum sollten Sie ein Versprechen abgeben, das Ihrem Anrufer überhaupt keinen Nutzen bringt und nur Ihre Implementierung einschränkt?
Beide sind absolut gültige Implementierungen derselben Funktion, sodass Sie nur unnötig eine Hand hinter den Rücken gebunden haben.
Darüber hinaus ist es ein sehr flaches Versprechen, das leicht (und rechtlich umgangen) werden kann.
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);
}
Schauen Sie, ich habe es trotzdem so implementiert, obwohl ich versprochen habe, es nicht zu tun - nur mit einer Wrapper-Funktion. Es ist wie wenn der Böse verspricht, jemanden in einem Film nicht zu töten und seinem Handlanger befiehlt, ihn stattdessen zu töten.
Diese überflüssigen Konstanten sind nicht mehr wert als ein Versprechen eines Filmbösewichts.
Aber die Fähigkeit zu lügen wird noch schlimmer:
Ich wurde aufgeklärt, dass Sie const in Header (Deklaration) und Code (Definition) nicht übereinstimmen können, indem Sie falsche const verwenden. Die const-happy-Befürworter behaupten, dies sei eine gute Sache, da Sie const nur in die Definition einfügen können.
// Example of const only in definition, not declaration
class foo { void test(int *pi); };
void foo::test(int * const pi) { }
Das Gegenteil ist jedoch der Fall ... Sie können eine falsche Konstante nur in die Deklaration einfügen und in der Definition ignorieren. Dies macht überflüssige Konstanten in einer API nur zu einer schrecklichen Sache und einer schrecklichen Lüge - siehe dieses Beispiel:
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
}
Alles, was die überflüssige Konstante tatsächlich tut, ist, den Code des Implementierers weniger lesbar zu machen, indem er gezwungen wird, eine andere lokale Kopie oder eine Wrapper-Funktion zu verwenden, wenn er die Variable ändern oder die Variable als Nicht-Konstanten-Referenz übergeben möchte.
Schauen Sie sich dieses Beispiel an. Welches ist besser lesbar? Ist es offensichtlich, dass der einzige Grund für die zusätzliche Variable in der zweiten Funktion darin besteht, dass ein API-Designer eine überflüssige Konstante eingegeben hat?
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;
}
}
Hoffentlich haben wir hier etwas gelernt. Überflüssige Konstante ist ein API-überfüllter Schandfleck, ein nerviger Nörgler, ein flaches und bedeutungsloses Versprechen, ein unnötiges Hindernis und führt gelegentlich zu sehr gefährlichen Fehlern.