余分な余分な const は、API の観点からは良くありません。
値によって渡される組み込み型パラメーターのコードに余計な const を追加すると、APIが乱雑になり、呼び出し元または API ユーザーに意味のある約束ができなくなります (実装を妨げるだけです)。
必要のないときに API に「const」が多すぎるのは、「泣いているオオカミ」のようなものです。最終的には「const」がいたるところにあり、ほとんどの場合何の意味もないため、人々は「const」を無視し始めます。
API の余分な const に対する "reductio ad absurdum" 引数は、これらの最初の 2 つの点に適しています。より多くの const パラメーターが適している場合、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);
上記のコード行を検討してください。宣言が雑然として長くて読みにくいだけでなく、API ユーザーは 4 つの「const」キーワードのうち 3 つを安全に無視できます。ただし、「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
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) // 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 を混乱させる目障りであり、厄介な問題であり、浅はかで意味のない約束であり、不必要な障害であり、時には非常に危険なミスにつながることもあります。