読者です 読者をやめる 読者になる 読者になる

ボレロ村上 - ENiyGmaA Code

中3女子です。

宣言に const がないということの利点について考える

※これはネタ記事です
※この記事の内容をコピペしたり参考にすることで、HDDが初期化されたり、鼻から悪魔が出たり、遠隔操作ウィルス事件の犯人として逮捕されたとしても、筆者は一切責任を負いません

C++ソースコードで、変数やメンバ関数の宣言に const がないということを否定的にとらえる人もいるかと思います。
特にローカル変数にもどんどん const を付けていこうというような信条を持った const教信者の人にとっては、const がないということが不安材料として目に映ることが多いのではないかと思います。


けれども、const がないということは、本当に素晴らしいことです。
const がないことによって、たくさんの面倒から解放されるからです。

どのような時点でも代入できる

まず基本的なこととして宣言に const がなければどのような時点でも代入できるということです。
つまり、定数であるかどうかを意識する必要がありません。

まずは、const がどれだけ邪魔な存在か見てみましょう。

#include <stdio.h>
using namespace std;

const char* const __MOJIRETSU__;    // error: uninitialized const ‘__MOJIRETSU__’
int main() {
    __MOJIRETSU__ = "f*ck";         // error: assignment of read-only variable ‘__MOJIRETSU__’
    __MOJIRETSU__[1] = 'u';         // error: assignment of read-only location ‘*(((const char*)__MOJIRETSU__) + 1u)’
    printf("%s\n", __MOJIRETSU__);
}

変数 __MOJIRETSU__ になんと2つも const がついています!
(最初の const がポインタの指す先に対する const で、後の const が変数 __MOJIRETSU__ そのものに対する const だそうです)
こんなややこしいことをするせいで、こんな短いプログラムなのにコンパイルエラーが3つも出てしまいました。

    1. const 変数は明示的に初期化しなければならないので、コンパイルエラー
    2. const 変数には再代入できないので、コンパイルエラー
    3. ポインタの指す先も const なので、コンパイルエラー
#include <stdio.h>
using namespace std;

char* __MOJIRETSU__;
int main() {
    __MOJIRETSU__ = "f*ck";
    __MOJIRETSU__[1] = 'u';
    printf("%s\n", __MOJIRETSU__);
}

このように const を消すだけでコンパイルが通ります。
実行はしていませんが、こんな短いプログラムなので、結果は見るまでもないでしょう。

記述量がとても短くなる

typedef const char* const* const* const* const* const* const* const* const* const* const* const* const* const* const* const* const __TYPE__;
typedef char**************** __TYPE__;

見比べてみれば一目瞭然です。

宣言 const がないと変更に強い

最初から const を書かなければ、後で定数を書き換えたくなったときも、わざわざキャストをしたり、元の宣言を書き換えたりする必要がありません。

#include <stdio.h>
using namespace std;

const char* const __MOJIRETSU__ = "";
int main() {
    printf("%s\n", __MOJIRETSU__);

    /* ここで書き換えたくなった。 */
    const_cast<char*&>(__MOJIRETSU__) = "f*ck"; // わざわざキャストしなければならない
    printf("%s\n", __MOJIRETSU__);
}

ちなみに、Cスタイルキャストはよく知らないけど使っては駄目らしいので、const_cast を使います。これだけで一気に C++ らしいコードになります。
もちろん、最初から const を書かなければ、こんな面倒をする必要はありません。

関数のオーバーロードが不要になる

#include <stdio.h>
using namespace std;

typedef struct __CLASS_TAG__ {
    int __VALUE__;
    int& __GET__() { return __VALUE__; }
    const int& __GET__() const { return __VALUE__; }
} __CLASS__;

__CLASS__ __HENSUU__;
int main() {
    printf("%d\n", __HENSUU__.__GET__());
}

このように、ゲッター関数には const をつける風習もあるようですが、そもそも const は必要ありません。

#include <stdio.h>
using namespace std;

typedef struct __CLASS_TAG__ {
    int __VALUE__;
    int& __GET__() { return __VALUE__; }
} __CLASS__;

__CLASS__ __HENSUU__;
int main() {
    printf("%d\n", __HENSUU__.__GET__());
}

見ての通り、constオーバーロードを消しても、まったく問題なくコンパイルできるし動作します。

宣言に const がないと変更して欲しくない変数がいつ変更されるかわからないという批判に答える

文脈を追って読むとどの変数が変更して欲しくないものか、たいていはすぐにわかります。
const がないソースコードを読むことに慣れてしまえば、何の問題もないと思います。

邪魔な const を排除するには

自分で書いたコードならともかく、サードパーティのライブラリには、困ったことに const を多用していたりするものがあります。
これを排除しましょう。


まずは先程も挙げたキャストです。

#include <stdio.h>
using namespace std;

typedef struct __CLASS_TAG__ {
    int __VALUE__;
    const int& __GET__() const { return __VALUE__; }
} __CLASS__;

__CLASS__ __HENSUU__;
int main() {
    reinterpret_cast<double&>(const_cast<int&>(__HENSUU__.__GET__())) = 3.14;
    printf("%d\n", __HENSUU__.__GET__());
}

const_cast や reinterpret_cast を活用することで、const を使ったクラスであっても自由自在に操作できます。

#include <stdio.h>
using namespace std;

#define const /* This is KOOL hack! */
typedef struct __CLASS_TAG__ {
    int __VALUE__;
    const int& __GET__() const { return __VALUE__; }
} __CLASS__;

__CLASS__ __HENSUU__;
int main() {
    __HENSUU__.__GET__() = 100;
    printf("%d\n", __HENSUU__.__GET__());
}

これは面白くて気の利いたハックです。
マクロで const を単なる空白に置換することで、無効化しているのです。

宣言に const がないことのデメリットはないのか

あげるとすれば、コンパイル時定数です。
配列変数を宣言するときの要素数を指定する場合などは、コンパイル時定数でなければなりません。
しかしこのような場合も、わざわざ定数を定義したりせず、マクロを使えばよいでしょう。


もうひとつは、いつどこで値が書き換えられるかまったく保証できないので危険ということですね。
これについては仕方ないので諦めてください。