Extra Überflüssige Konstanten sind aus API-Sicht schlecht:
Einfügen zusätzlicher überflüssiger const's in Ihren Code für Parameter des intrinsischen Typs, die als Wert übergeben werden verstopft Ihre API ohne dem Aufrufer oder API-Benutzer ein sinnvolles Versprechen zu geben (es erschwert nur die Implementierung).
Zu viele 'const' in einer API, die nicht benötigt werden, sind wie " weinende Wölfe Irgendwann werden die Leute anfangen, den Begriff "const" zu ignorieren, weil er überall vorkommt und meistens nichts bedeutet.
Die "reductio ad absurdum" Argument zu extra consts in API sind gut für diese ersten beiden Punkte wäre, wenn mehr const Parameter gut sind, dann jedes Argument, das eine const auf sie haben kann, SOLLTE eine const auf sie haben. In der Tat, wenn es wirklich so gut wäre, würden Sie wollen, dass const der Standard für Parameter und haben ein Schlüsselwort wie "mutable" nur, wenn Sie den Parameter ändern möchten.
Versuchen wir also, überall, wo es möglich ist, ein Hindernis zu schaffen:
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 Code-Zeile. Die Deklaration ist nicht nur unübersichtlicher, länger und schwieriger zu lesen, sondern drei der vier "const"-Schlüsselwörter können vom API-Benutzer getrost ignoriert werden. Die zusätzliche Verwendung von "const" hat jedoch dazu geführt, dass die zweite Zeile potenziell GEFÄHRLICH!
Warum?
Ein kurzer Lesefehler beim ersten Parameter char * const buffer
könnte den Eindruck erwecken, dass der Speicher im Datenpuffer, der übergeben wird, nicht verändert wird - dies ist jedoch nicht der Fall! Überflüssige 'const' können zu gefährlichen und falschen Annahmen über Ihre API führen wenn sie schnell gescannt oder falsch gelesen werden.
Überflüssige Konstanten sind auch unter dem Gesichtspunkt der Code-Implementierung 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, dann "verspricht" die API, die Funktion nicht auf die erste Art und Weise unten 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 geben, das Ihrem Aufrufer keinerlei Nutzen bringt und nur Ihre Implementierung einschränkt?
Beides sind absolut gültige Implementierungen derselben Funktion, so dass Sie sich nur unnötig eine Hand auf den Rücken gebunden haben.
Außerdem ist es ein sehr oberflächliches 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);
}
Ich habe es trotzdem so implementiert, obwohl ich versprochen hatte, es nicht zu tun - nur mit einer Wrapper-Funktion. Das ist so, wie wenn der Bösewicht in einem Film verspricht, niemanden zu töten und stattdessen seinen Handlangern befiehlt, ihn zu töten.
Diese überflüssigen Consts sind nicht mehr wert als ein Versprechen eines Film-Bösewichts.
Aber die Fähigkeit zu lügen wird noch schlimmer:
Ich bin darüber aufgeklärt worden, dass man const im Header (Deklaration) und im Code (Definition) durch die Verwendung von spurious const falsch zuordnen kann. Die Befürworter von const behaupten, dass dies eine gute Sache ist, da man const nur in der Definition verwenden kann.
// Example of const only in definition, not declaration
struct 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 aufnehmen und sie in der Definition ignorieren. Dies macht überflüssige const in einer API nur noch schrecklicher und zu einer furchtbaren Lüge - siehe dieses Beispiel:
struct 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 const bewirkt, ist, dass der Code des Implementierers weniger lesbar wird, da er gezwungen ist, eine weitere lokale Kopie oder eine Wrapper-Funktion zu verwenden, wenn er die Variable ändern oder die Variable durch eine Nicht-Konst-Referenz übergeben will.
Sehen Sie sich dieses Beispiel an. Was 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 eingefügt 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 daraus etwas gelernt. Überflüssige const ist ein API-verstopfender Schandfleck, ein lästiges Ärgernis, ein oberflächliches und bedeutungsloses Versprechen, ein unnötiges Hindernis und führt gelegentlich zu sehr gefährlichen Fehlern.