余分な余分なconstは、APIの観点からは悪いです。
値によって渡される組み込み型パラメーターの余分なconstをコードに追加すると、APIがすっきりしますが、呼び出し元またはAPIユーザーに意味のある約束はありません(実装を妨げるだけです)。
APIで不要な「const」が多すぎると「泣いているオオカミ」のようになり、最終的には「const」を無視し始めます。なぜなら、それはあちこちにあり、ほとんどの場合何も意味しないからです。
APIの追加のconstに対する「reductio ad absurdum」引数は、これらの最初の2つの点に適しています。より多くのconstパラメータが適切である場合、constを持つことができるすべての引数にはconstが必要です。実際、本当にそれが良ければ、constをパラメーターのデフォルトにして、パラメーターを変更する場合にのみ「可変」のようなキーワードを設定することをお勧めします。
だからできる限りconstを入れてみよう:
void mungerum(char * buffer, const char * mask, int count);
void mungerum(char * const buffer, const char * const mask, const int count);
上記のコード行を検討してください。宣言がすっきりし、長くて読みにくいだけでなく、4つの 'const'キーワードのうち3つは、APIユーザーが安全に無視できます。ただし、「const」を余分に使用すると、2行目が危険になる可能性があります。
どうして?
最初のパラメーターをすぐに読み間違えると、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がtrueでない場合、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を不一致にできることを悟りました。constを支持する支持者は、constを定義にのみ入れることができるため、これは良いことだと主張しています。
// Example of const only in definition, not declaration
class foo { void test(int *pi); };
void foo::test(int * const pi) { }
ただし、その逆は真です...偽のconstを宣言にのみ入れて、定義では無視できます。これは、APIの余分なconstを恐ろしいものにして恐ろしい嘘にするだけです。次の例を参照してください。
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が実際に行うことは、変数を変更したり、非const参照で変数を渡したりしたいときに、実装者のコードを読みにくくすることです。
この例を見てください。どちらがより読みやすいですか?2番目の関数に追加の変数がある唯一の理由は、一部の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;
}
}
うまくいけば、ここで何かを学びました。余分なconstは、APIが散らかった目障り、迷惑なナグ、浅くて意味のない約束、不必要な妨害であり、非常に危険なミスにつながることがあります。