constのあれこれ2const...このたった一つのキーワードは、ライブラリ全体の振る舞いを統括する恐るべき力を持っています。C++踏み込み編その2「constのあれこれ」ではconstの機能について見てきました。この章ではそれをおさらいしつつ、constが発するメッセージにさらに耳を傾ける事にしましょう。constルールconstは「その値を以後変更できなくする」という印です。ここでいう「その値」とは、文字通り変数に入っている値です。erorr!実体(i0)そして参照(ri0)はわかります。ポインタは要注意です。上のようにポインタの先にある値を変更する事はできません。error!こうするとそのポインタはポインタ自体も変更出来ませんし、参照先の値を変更することもできなくなります。かんなりガッチガチになるわけです。ポインタのconstには弱点があります。0;}Valという構造体の中に実体a、ポインタpA、そして構造体のポインタ*childがあります。testFunc関数の引数でconstを付けたValのポインタを渡してもらった時に、�@、�A、�Bは全部エラーになるでしょうか?これは�@は「constがついてるからaは変更できませんよ!」と怒られます。�Aと�Bはセーフなんです。int*型の変数を一つ作り初期化しています。constポインタなので参照先の変更はできません。600;」などとするとコンパイラに怒られます。これをtest2Funcに渡しました。ここでポインタ変数a自体を書き換えてしまいました!戻ってきたaは別物で、参照先*aも100に変わってしまっています。このコード、ちゃんとコンパイルも通るし正しく動きます。関数の引数としてのconst関数の引数にconstを付けた場合、その引数自体は変更できなくなります。0;}func関数の引数a及びbは値渡しですが、bはそれをconstとしてコピーします。すると、関数内では変更ができない値として扱えます。bに代入しようとするとエラーになります。0;}constが付いたポインタ引数の場合、ポインタの先を変更しようとすると怒られます。ポインタ変数そのものには違う値を代入できてしまいます。これも�@の最後で説明したルールの通りです。0;}今までのルールで考えると、cosnt付きポインタが引数場合、それが指す先の値の変更は不可でした。よって、上のように配列演算子を使って値を変更しようとしてもエラーとなってしまいます。引き渡した文字列を変更されたくない場合はconstを付ければ良いという事です。「文字列の配列」の場合はどうでしょう。0;}ぶは!何ということでしょう、関数の中で引数で渡した文字列変更が許可されてしまいました。"Str3"」という文字列に変わってしまいます。"P"はfunc関数の中で引き渡されたローカルな場所にあるため、いずれかき消されます。ダブルポインタのconstがガードするのは実体だけなんです:これはトリプルポインタ以降も同じです。0;}さぁ出ました、C言語の訳分からん世界。上のはどう読み取れば良いのでしょうか。最初に付いているconstは「ポインタの実体をガードしますよ〜」という意味です。char*型の次に来ているconstはその一つ前の値をガードしますよという意味になります。分かりにくーい(笑)。上の場合**str自体は変更ができてしまいます。0;}ぶふっと笑ってしまいますが本当です。これ、どうして引渡しできているのでしょうか?関数に引き渡すときには、元の値をより固くガードするコピーはOKなんです。逆に元々constで守られていた変数をオープンにしてしまうような引渡しはNGなんです。上のconst祭りな引数はより堅牢にしているだけなのでOKです。最初のconstを外したとすると、実体へのガードが無くなってしまうのでNG。関数の戻り値としてのconstconstは関数の戻り値にも付ける事ができます。0;}main関数内ではfunc関数の戻り値をint型で受けています。これ、大丈夫なのでしょうか?実際にコンパイルは通ります。?constだった値をint型に「コピー」しているからOKなんです。vを宣言と同時に初期化しているのでOKなんです。vはconstなので宣言時に初期化しないとエラーになってしまうためです。constを外せばOK。0;}これはエラーです。それをint型の参照にするというのは、戻り値のconstを外す行為です。ガードが切れるのでエラーというわけ。kariya_mitsuruさんにご指摘頂きましたconstの付いていない参照を数値等の右辺値でダイレクトに初期化する事はできません。300;と初期化しようとしているようなもので、これは無理です。0;}int型の場合、参照で受けようがコピーしようが大した差は無いのですが、構造体の場合はコピーは避けたい所。0;}constを外したらもちろんエラー。constを外す場合は参照ではなくてコピーとして受けなければなりません。constを考えるときには常に「ガードを外す代入はエラー」と覚えておくとすっきりします。クラスのメソッドに付くconstconstはクラスのメソッドにも付ける事ができます。これがまた頭がごっちゃになってしまうんです。val;}};メソッドの最後にconstが付きます。これ、どういう意味になるかというと「メソッドの中でメンバ変数を変更できませんよ〜」という意味になります。val;}};などとメンバを変更しようとするとエラーになってしまいます。それだけでなく、参照を返す場合にはそれもconstであることを強要されます。戻り値のconstを外してもエラーになってしまうんです。val;}};これ「セーフ」なんです。「えーー!なんでじゃー」となりますが、セーフなんです。メソッドの後のconstは、あくまでもメンバ変数そのものの変更をガードしています。参照先の事などは知らんのです。メソッドの後ろのconstの機能として、もう一つ厳しいと言うか厳格なガードがあります。それは「constが付いたメソッドしか呼べない」という物です。ポインタを変換できません。」というエラーになります。これはですね、constが付いたメソッド内では、自分自身がconstが付いた状態に変化します。MyClass'です。それに対してsetValメソッドにはconstが付いていません。クラスのメソッドは暗黙の了解として第0引数にMyClass&という参照の引数を持っていて、メソッドを呼び出す時に自分自身を第0引数に引き渡しています(*thisが渡されている)。自分自身がconst付きなのに、第0引数はconstが付いていないのでガードが外れる方向の代入になるわけです。エラーとなってしまいます。メソッドの後ろにconstを付けるというのは、非常に厳格にメンバを変更しないという覚悟の元で付けないといけないんです。val;}};これ、どういう時に重宝するかと言うと、constが付いたメソッド内でも呼びたいし、付いていないメソッド内で引数の参照を通して値を変更したい場合に欲しいんです。it->second;}};MyClass2::funcメソッドはconst無しとconst付きの2タイプがあります。constが付いていない方ではメンバメソッドの変更が可能です。そのため普通のiteratorを通してマップの値を変更できます。所が、const付きのfuncメソッドの場合、iteratorでfindメソッドの戻り値を受ける事ができません。これはconst付きのmap:findメソッドが呼ばれて戻り値がconst_iteratorになっているためです。厳格に、値が変更できないイテレータで、secondもconst付きの値になっています。const付きのfuncが呼ばれるconstが付いていないMyClass2オブジェクトだとconst無しのfuncメソッド、const付きのMyClass2オブジェクトだとconst付きのfuncメソッドがそれぞれ呼び分けられます。オーバーロード演算子などはconst付きでもconst無しでも呼ばれる場合があるため、両方に対応した演算子をオーバーロードしておかないと変なことになります。クラスにコピーコンストラクタを定義しなかった場合はコンパイラが自動的に作成します。コピーコンストラクタの引数にconstが付いている意味は、もうお分かりの通りで、コピー元を一切汚さない為です。コピー元を何も変更せずに、自分自身にコピー元の値を全部ディープコピーするのがコピーコンストラクタの役目です。一般変数(int型など)はそのまま値コピーしますし、クラスのオブジェクトなどはそのクラスが持つコピーコンストラクタを用いて値をコピーします。私達は「参照クラスがポインタを持っていた場合、そのポインタの先は変更できる」という事を学びました。400;」という代入はできてしまいます。これをうまく利用すると、スマートポインタの参照カウンタなどを操作できるわけです。constのあれこれ、どうでしたでしょうか?変更したくても出来ないというのは、頑健なライブラリには必要になる事もあります。それは一方で強固な束縛でもあります。constの付け方でライブラリの全体の流れや機能すら決められてしまうというのは、まさにconstパワーの恐ろしさなのです。疎かに扱うと泣きを見るのがconstです。