Зайві зайві показники погані з точки зору API:
Внесення зайвих зайвих const у свій код для внутрішніх параметрів типу, переданих значенням, захаращує ваш API , не даючи жодних значущих обіцянок абоненту або користувачеві API (це лише перешкоджає реалізації).
Занадто багато "const" в API, коли це не потрібно, схоже на " плач вовка ", з часом люди почнуть ігнорувати "const", оскільки це всюди і нічого не означає, що більшість часу.
Аргумент "reductio ad absurdum" для додаткових consts в API хороший для цих перших двох пунктів. Це якщо б більше параметрів const були хорошими, то кожен аргумент, який може мати на ньому const, повинен бути const на ньому. Насправді, якби це було дійсно так добре, ви хочете, щоб const був типовим для параметрів, а ключове слово типу "mutable" було б лише тоді, коли ви хочете змінити параметр.
Тож давайте спробуємо ввести в дію будь-коли:
void mungerum(char * buffer, const char * mask, int count);
void mungerum(char * const buffer, const char * const mask, const int count);
Розглянемо рядок коду вище. Не тільки декларація є більш захаращеною та довшою та складнішою для читання, але три з чотирьох ключових слів «const» можуть бути ігноровані користувачем API. Однак додаткове використання «const» зробило другий рядок потенційно небезпечним!
Чому?
Швидкий непрочитаний перший параметр char * const buffer
може змусити вас думати, що він не змінить пам'ять у буфері даних, який передається - однак це неправда! Зайвий "const" може призвести до небезпечних та неправильних припущень щодо вашого API при швидкому скануванні чи неправильному читанні.
Надмірна ступінь погана і з точки зору впровадження коду:
#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);
Якщо FLEXIBLE_IMPLEMENTATION не відповідає дійсності, то API "обіцяє" не реалізовувати функцію першим способом нижче.
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];
}
}
Це дуже дурне обіцянку зробити. Чому варто давати обіцянку, яка взагалі не приносить користі вашому абоненту і обмежує вашу реалізацію?
Обидві ці ідеально реалізуються однієї і тієї ж функції, хоча все, що ви зробили, без зайвих зусиль зав'язуєте однією рукою за спину.
Крім того, це дуже неглибока обіцянка, яку легко (і законно обійти).
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);
}
Подивіться, я так чи інакше реалізував це, хоча пообіцяв цього не робити - просто використовуючи функцію обгортки. Це як коли поганий хлопець обіцяє не вбивати когось у кіно і наказує своєму прихильникові вбити їх замість.
Ці зайві конкурси варті не більше, ніж обіцянки з фільму-поганого хлопця.
Але здатність брехати стає ще гіршою:
Я просвітився, що ви можете невідповідно const у заголовку (декларації) та коді (визначенні), використовуючи помилкові const. Захисні адвокати твердять, що це хороша річ, оскільки вона дозволяє встановлювати const лише у визначенні.
// Example of const only in definition, not declaration
class foo { void test(int *pi); };
void foo::test(int * const pi) { }
Однак зворотне вірно ... ви можете поставити помилковий const тільки в декларації і проігнорувати його у визначенні. Це лише робить зайвий const в API більш жахливим і жахливою брехнею - див. Цей приклад:
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
}
Все зайве const насправді полягає в тому, щоб зробити код реалізатора менш читабельним, змусивши його використовувати іншу локальну копію або функцію обгортки, коли він хоче змінити змінну або передати змінну за допомогою non-const посилання.
Подивіться на цей приклад. Що легше читати? Очевидно, що єдиною причиною додаткової змінної у другій функції є те, що якийсь дизайнер API кинув зайвий const?
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;
}
}
Сподіваємось, ми щось тут дізналися. Зайвий конст - це перенапружений очей, роздратований API, дратівливий гнів, неглибока і безглузда обіцянка, непотрібна перешкода, а іноді призводить до дуже небезпечних помилок.