Dodatkowe zbędne stałe są złe z punktu widzenia API:
Umieszczenie w kodzie dodatkowych zbędnych stałych dla parametrów typu wewnętrznego przekazywanych przez wartość zaśmieca interfejs API , nie dając żadnej znaczącej obietnicy użytkownikowi wywołującemu lub użytkownikowi interfejsu API (utrudnia to tylko implementację).
Zbyt wiele „stałych” w interfejsie API, gdy nie są potrzebne, jest jak „ płaczący wilk” ”, w końcu ludzie zaczną ignorować „stały”, ponieważ jest wszędzie i nic nie znaczy przez większość czasu.
Argument „reductio ad absurdum” do dodatkowych stałych w API jest dobry dla tych dwóch pierwszych punktów byłoby, gdyby więcej parametrów stałych było dobrych, to każdy argument, który może mieć na nim stałą, POWINIEN mieć na sobie stałą. W rzeczywistości, gdyby naprawdę był tak dobry, chciałbyś, aby const był domyślnym parametrem i miał słowo kluczowe takie jak „mutable” tylko wtedy, gdy chcesz zmienić parametr.
Spróbujmy więc wprowadzić const gdziekolwiek możemy:
void mungerum(char * buffer, const char * mask, int count);
void mungerum(char * const buffer, const char * const mask, const int count);
Rozważ wiersz kodu powyżej. Deklaracja jest nie tylko bardziej zaśmiecona, dłuższa i trudniejsza do odczytania, ale trzy z czterech „stałych” słów kluczowych może bezpiecznie zignorować użytkownik interfejsu API. Jednak dodatkowe użycie „const” sprawiło, że druga linia potencjalnie NIEBEZPIECZNA!
Dlaczego?
Szybkie błędne odczytanie pierwszego parametru char * const buffer
może sprawić, że pomyślisz, że nie zmodyfikuje on pamięci w przekazywanym buforze danych - nie jest to jednak prawda! Zbędne „stałe” mogą prowadzić do niebezpiecznych i niepoprawnych założeń dotyczących interfejsu API podczas szybkiego skanowania lub błędnego odczytu.
Zbędne const są również złe z punktu widzenia implementacji kodu:
#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);
Jeśli FLEXIBLE_IMPLEMENTATION nie jest prawdą, to interfejs API „obiecuje”, że nie zaimplementuje funkcji w pierwszy sposób poniżej.
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];
}
}
To bardzo głupia obietnica. Dlaczego należy składać obietnicę, która w ogóle nie przyniesie korzyści dzwoniącemu, a jedynie ograniczy wdrożenie?
Oba są doskonale poprawnymi implementacjami tej samej funkcji, więc niepotrzebnie przywiązujesz jedną rękę do pleców.
Co więcej, jest to bardzo płytka obietnica, którą łatwo (i legalnie obchodzi).
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);
}
Słuchaj, zaimplementowałem go w ten sposób, mimo że obiecałem, że tego nie zrobię - tylko używając funkcji otoki. To tak, jakby złoczyńca obiecał, że nie zabije kogoś w filmie, i zamiast tego poleci swojemu poplecznikowi.
Te zbędne const są warte nie więcej niż obietnica złego filmowca.
Ale zdolność do kłamstwa staje się jeszcze gorsza:
Zostałem oświecony, że można niedopasować const w nagłówku (deklaracji) i kodzie (definicji) za pomocą fałszywego const. Zwolennicy const-happy twierdzą, że jest to dobra rzecz, ponieważ pozwala ona umieszczać const tylko w definicji.
// Example of const only in definition, not declaration
class foo { void test(int *pi); };
void foo::test(int * const pi) { }
Jednak odwrotność jest prawdziwa ... możesz umieścić fałszywą stałą tylko w deklaracji i zignorować ją w definicji. To sprawia, że zbędna stała w interfejsie API staje się bardziej okropną rzeczą i okropnym kłamstwem - zobacz ten przykład:
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
}
W rzeczywistości wszystko, co jest zbędne, powoduje, że kod implementatora jest mniej czytelny, zmuszając go do użycia innej lokalnej kopii lub funkcji opakowania, gdy chce zmienić zmienną lub przekazać zmienną przez odwołanie inne niż const.
Spójrz na ten przykład. Który jest bardziej czytelny? Czy jest oczywiste, że jedynym powodem dodatkowej zmiennej w drugiej funkcji jest to, że jakiś projektant API wrzucił zbędną stałą?
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;
}
}
Mam nadzieję, że czegoś się tutaj nauczyliśmy. Zbędny const to zaśmiecony API, denerwujący nag, płytka i bezsensowna obietnica, niepotrzebna przeszkoda, a czasami prowadzi do bardzo niebezpiecznych błędów.