ゆとりーなの日記

日記的な事を書いて行くと思はれる

const教信者が頭の可愛そうな人たちと言われないために

ローカル変数に対するconst - melpon日記 - HaskellもC++もまともに扱えないへたれのページにて、厳しいご指摘をいただいたので、ここでconstを付ける意義をしっかりとまとめておきたいと思います。ただ妄信的にconstを付ける頭の可愛そうな人と思われないためには、constを付ける意義を的確に説明できとかないとというわけです。実際周りにもconst教???って人が多いので。
ここではconst教の真髄であると思われる、ローカル変数、値渡しの引数、戻り値に対してconstを付けることについて触れることにします。#define代わりのconst、const参照渡し、constメンバ関数等は、const教でなくても誰もがやってくれていることだと信じているので触れません。

constの意義

まず、constを付ける意義としては、以下のものが挙げられるでしょう

  • 変数にある値を束縛したことの表明
  • 禁止したいことを禁止する手段
  • あわよくば最適化を期待する

ローカル変数へのconst付け

ローカル変数にconstを付ける意義は一番上の理由が大きいでしょう。melpon氏が言われたように、美しいプログラムでは関数等は数行で終わらせることが多くなるので、constを付けることで代入を防止できたとか、値が一度束縛されるとその値のまま変わらないことが見えて安心だとか思うことはあまりありません。ぱっと見ですぐに分かりますから。だから正直付いてなくても困ることはないんですよね。しかしそれでもconstを付けるのは、やはり変数に正しい意味づけを与えると幸せになれるからです。一度値を束縛してあとは参照するだけならその変数にはconstを付けてあげることで、役割を明確にできるのです。
そして、const教信者が全てのローカル変数にconstを付けることを推奨する要因がここにあります。実際プログラムを書いてみると、ローカル変数は意外と一度値を束縛してあとは参照するだけってパターンが多いのです。そして、一見そういうローカル変数ではなくても、少し考えてプログラムを修正してやると、上のパターンに持っていけるローカル変数も多いのです。もちろんどうがんばってもそうならないパターンもあるので、それにはconstを付ける必要はありません。C++は別にHaskellではないので、無理に全部そういうことしなくてもいいということは認識しておくべきです。
ちなみにローカル変数にconstを付けるときに一番困るのは次のパターンです。C言語用のAPIの中には、戻り値がエラーコードで、値はアドレスを渡して書き変えてもらうというものが案外あります。

int val;
if (!getVal(&val)) {
    // エラー処理
}
// 以降valは参照されるだけ

このようなパターンの場合、valには変数の意味的にもconstを付けてやりたいところですが、残念ながら付けてやることはできません。こういうのを見ると、無性に悔しくなります。

値渡し引数へのconst付け

続いて値渡しの引数にconstを付ける意義です。これもローカル変数の時と同じです。むしろ引数の方がよりconstらしい性質を持っている気がします。引数はいわば入力なので、入力値で引数を束縛してあとは参照するだけというパターンが殆どでしょう。引数を直接いじるパターンなんてのはそうそうありません。参照渡しの多くにconstがつくのと同じです。参照渡しの時と違い、値渡しの引数を書き変えても外部的には何も変わらないこと、宣言では普通値渡しの引数にconstを付けないので定義と違って見えるのが嫌だということから、値渡しの引数にconstを付けることに意義を感じない人は多いみたいですが、値渡しの引数も意味的にconstなことが殆どなので、それらについてはやはりconstを付けてやることには意義があると思います。

戻り値へのconst付け

最後に戻り値のconst付けに付いてです。これについては基本的に付ける必要はないと思います。むしろ付けない方がいいと思うくらいです。戻り値にconstを付けると、ムーブセマンティクスが効かなくなってしまうからです。加えて戻り値にconstを付けても、意味的に特に有意義なことはないように思えます。
唯、その戻り値が演算子オーバーロードだった場合は別です。例えば次のようなクラスがあったとします。

class Integer {
 public:
    explicit Integer(int i);
    Integer operator +(const Integer &rhs);
 private:
    int i_;
};

Integer x(1), y(2), z(3);
x + y = z; // この意味不明な式が通る

このままだと組み込み型の演算では通らない、x + y = zという式がコンパイルエラーなく通ってしまいます。この手の挙動はできるだけ組み込み型の挙動に合わせるべきです。そこで、次のように、

class Integer {
 public:
    explicit Integer(int i);
    const Integer operator +(const Integer &rhs);
 private:
    int i_;
};

Integer x(1), y(2), z(3);
x + y = z; // この意味不明な式がコンパイルエラーになる

operator +の戻り値の型にconstを付けてやると、この意味不明な式をコンパイルエラーにできます。ここでのconstを付ける意義は二番目の理由、禁止したいことを禁止することにあります。
以上でconst教駆け出し信者の布教活動を終わらせていただきます。いかんせん新参なもので、理解が足らずおかしなことを言っているかもしれませんが、何かあればコメントの方で。