稲枝の押入れ

いなえが適当なことを書いては、しまっておく場所

ポインタにconstをつけた時書き換え不能になる変数

超絶初歩的だが、constをつけた時のポインタの表す意味がたまにわからなくなるので自分用メモ。

int型へのポインタpは以下のように書ける

int * p;

この時*が型もしくは変数名についていることがあるが、それについては特に区別しない。

つまり、

[型名] * [変数名];

と書けるわけだが、このポインタ変数にconstをつけることを考えると、候補となる位置は

(1) [型名] (2) * (3) [変数名] (4);

が有り得そうだ。そして、実際に(1),(2),(3)の位置にはconstを置くことが出来る。

そして、このconstの位置によって、何にconstを付与しているかの意味が変わってくる。

具体的には、

  • ポインタ変数が保持している対象の変数のアドレス(=ポインタ変数の値そのもの)
  • ポインタ変数によってアドレスが保持されているオブジェクト(=ポインタ変数が指す先)

に対するconstの付与という2種類の付与の意味がある。ここでは前者を「指示変数」、後者を「被指示変数」と呼ぶことにする(一般的な呼び方ではなく、文章が長くなるのを避ける為の便宜的なもの)。

f:id:makiofinae:20190511115823p:plain
指示変数と被指示変数

実際に、上記(1),(2),(3)の3箇所について、constを置くことを考えると

//const1つ
const int       *       p;
      int const *       p;
      int       * const p;

//const2つ
const int const *       p;
const int       * const p;
      int const * const p;

//const3つ
const int const * const p;

の組み合わせが考えられるが、これらそれぞれについて表にすると、

宣言 指示変数の書き換え 被指示変数の書き換え
//const1つ
const int * p; ×
int const * p; ×
int * const p; ×
//const2つ
const int const * p; - -
const int * const p; × ×
int const * const p; × ×
//const3つ
const int const * const p; - -

となる(-になっているところはそもそも定義不能)。

簡単な確認コードは以下。

int main(){
    int hoge = 0;
    
    int * ip = &hoge;
    
    const int * cip = &hoge;
    int const * icp = &hoge;
    int * const ipc = &hoge;
    
    //const int const * cicp = &hoge;//error: duplicate 'const'
    const int * const cipc = &hoge;
    int const * const icpc = &hoge;
    
    //const int const * const cicpc = &hoge; //error: duplicate 'const'
    
    int fuga = 1;
    
    cip = &fuga;
    //*cip = 2; //error: assignment of read-only location '* cip'
    
    icp = &fuga;
    //*icp = 2; //error: assignment of read-only location '* icp'
    
    //ipc = &fuga; //error: assignment of read-only variable 'ipc'
    *ipc = 2;
    
    //cipc = &fuga; //error: assignment of read-only variable 'cipc'
    //*cipc = 2; //error: assignment of read-only location '*(const int*)cipc'
    
    //icpc = &fuga; //error: assignment of read-only variable 'icpc'
    //*icpc = 2; //error: assignment of read-only location '*(const int*)icpc'
}

ここまで見るとややこしいように見えるかもしれないが、簡単な覚え方があって、*より前のconstは被指示変数の書き換えを禁止し、*より後のconstは指示変数の書き換えを禁止すると覚えるといい。

f:id:makiofinae:20190511115922p:plain
constの位置と書き換え禁止対象

因みに上の確認コードのコメントにもあるように、同じ意味のconst付与が重複するとエラーになるので注意。

*より前のconstって指示変数と被指示変数、どっちの書き換えを禁止するんだっけ…となったら参照を思い出すといいかも。const付き参照を書く時は

const int ref& = hoge;

となるが、参照はどのオブジェクトを参照するかの書き換えはconstがついていなくても出来ない(=ポインタと同じように考えるなら指示変数は書き換えできないということ)。つまり、わざわざconstをつけるという事は、参照先の被指示変数の書き換えを禁止していることを意味している。 これをポインタの先程の覚え方と同じように「&より前にconstがある時は参照先の被指示変数を書き換えられなくする」と無理矢理に解釈すれば、「ポインタも*より前にconstがある時は被指示変数を書き換えられなくする」と思い出せる。

以上はオブジェクトへのポインタの意味の覚え方だが、もう少し汎用的にポインタを読めるようになりたいのであれば、ポインタ完全制覇に載っているので、こちらを読むことをおすすめする。