从API的角度来看,过多的无意义const是不好的:
在代码中为传递的内在类型参数传递额外的无意义const会混淆您的API,同时对调用者或API用户没有任何有意义的承诺(它只会妨碍实现)。
当API中没有必要使用太多“const”时,就像“哭狼”一样,最终人们会开始忽略“const”,因为它到处都是,大部分时间都没有意义。
对于API中额外的常量的“归谬法”论据对这前两点的好处是,如果更多的常量参数是好的,那么每个可以有const的参数都应该有const。事实上,如果真的那么好,您希望const成为参数的默认值,并且只有在想要更改参数时才有像“mutable”这样的关键字。
因此,让我们尝试在所有可能的地方都加入const:
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进行扫描或快速误读时产生危险和不正确的假设。
从代码实现的角度来看,多余的const也是不好的:
#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)
{
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。 热衷于const的倡导者声称这是一件好事,因为它让您只在定义中放置const。
struct foo { void test(int *pi); };
void foo::test(int * const pi) { }
然而,相反的情况是真实的... 你可以在声明中添加一个虚假的const并在定义中忽略它。这只会使API中不必要的const变得更加糟糕和虚伪-参见以下示例:
struct foo
{
void test(int * const pi);
};
void foo::test(int *pi)
{
pi++;
}
所有多余的const实际上只会使实现者的代码变得更加难以阅读,因为它强制他在想要更改变量或通过非const引用传递变量时使用另一个本地副本或包装函数。
看这个例子。哪个更容易理解?很明显第二个函数中额外变量的唯一原因是某个API设计者添加了一个多余的const。
struct llist
{
llist * next;
};
void walkllist(llist *plist)
{
llist *pnext;
while(plist)
{
pnext=plist->next;
walk(plist);
plist=pnext;
}
}
void walkllist(llist * SUPERFLUOUS_CONST plist)
{
llist * pnotconst=plist;
llist *pnext;
while(pnotconst)
{
pnext=pnotconst->next;
walk(pnotconst);
pnotconst=pnext;
}
}
希望我们从中学到了一些东西。过多的const会让API变得混乱不堪,烦人的催促,毫无意义的浅薄承诺,是一种不必要的阻碍,并且有时会导致非常危险的错误。