ポインタ定数#
int *const ptr = nullptr;
const
はポインタ ptr
の値を修飾しており、ptr
自体は変更できないため、ポインタ定数は初期化する必要があります。また、ptr
の値を変更することはできません。
int a = 10;
int *const ptr = &a;
ptr = nullptr; // エラー
ポインタ定数の使用例#
例えば、2 つの整数の値をポインタを介して交換する関数を作成する必要があるとします。
以下は実装コードです:
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
上記のコードは機能を実現できますが、問題があります。関数内でポインタの指す値を変更したり、ポインタの値を変更したりできるため、コードが強固ではありません。コードをより堅牢にするために、ポインタ定数を使用することができます。
以下は修正後のコードです:
void swap(int *const a, int *const b) { ... }
定数ポインタ#
const int *ptr;
// または
int const *ptr;
const
は *ptr
の値、つまりポインタが指す値を修飾しており、ptr
が指す値は変更できません。また、定数型変換を行わない限り、*ptr
を変更することはできません。
int a = 10;
int const *ptr = &a;
*ptr = 20; // エラー
定数ポインタの使用例#
例えば、配列を出力する関数を作成する必要があるとします。
以下は実装コードです:
void printArray(int const* arr, size_t len) {
for (size_t i = 0; i < len; ++i) {
std::cout << arr[i] << " ";
}
}
ここで、配列から退化したポインタの値を const
修飾するだけで十分だと思うかもしれませんが、まだ問題があります。関数内でポインタの値を変更することができるため、これは望ましくありません。したがって、ポインタの値にも const
を修飾する必要があります。
以下は修正後のコードです:
void printArray(int const *const arr, size_t len) { ... }
ポインタ定数と定数ポインタの関係#
定数ポインタには 2 つの書き方があることに気づくでしょう:int const *
と const int *
。
最初の形式の理解について:const
は左側を修飾するものであり、例えば int const *ptr;
では const
は値の型 int
を修飾しているため、ポインタが指す値を変更できません。int *const ptr = nullptr;
では const
は int *
ポインタ型を修飾しているため、ポインタが指す先を変更できません。この理解はコードの規約に従う必要があります。なぜなら、定数ポインタを const int *
のように書くと変換が必要になるからです。(または、左側にオブジェクトがない場合、右側のオブジェクトを修飾すると解釈することもできます。)
定数ポインタとポインタ定数の命名は const int *
と int *const
のコード規約に対応しています。
以下のコードを見てみましょう:
#include <iostream>
struct A {
void foo() { std::cout << "non const" << std::endl; }
void foo() const { std::cout << "const" << std::endl; }
};
int main() {
A a;
A *const ptr1 = &a;
ptr1->foo();
A const *ptr2 = &a;
ptr2->foo();
}
int *const ptr1 = &a
では const
は ptr1
ポインタの値を修飾しており、ポインタが指すオブジェクトを修飾していません。そのため、ptr1->foo()
は非 const
バージョンを呼び出します。
int const *ptr2 = &a
では const
は ptr2
ポインタが指す値 a
を修飾しており、指す値を変更できないことを意味します。そのため、ptr2->foo()
は const
バージョンを呼び出します。
もし間違いがあれば、指摘してください。