【第二言語としてのC++】1. 値, ポインタ, 参照

Pythonのような高級な言語をある程度習得し, プログラミングというものをなんとなく理解しているという前提で, C++に固有の仕様について説明します.

今回はC/C++の「*」とか「&」とかいう記号について説明します.

## データはどこにあるのか
まず, PCを組み立ててください. PCを組み立てたことがない人は, PCを組み立てる動画でも見てきてください. PCの構造が理解できましたか? 理解できたという前提で話を進めます.

プログラム上で現れるデータはメモリ上のどこかに必ず存在します. Pythonではこれを見えなくしているので意識してこなかったかもしれませんが, すべてのデータはCPUの隣に差してあるメモリ上のどこかに物理的に書きこまれます. そのため, すべてのデータは次の二つの情報によって決定されます.
- どこからどこまでに書かれているか.
- 何が書かれているか.

「どこから」という情報を**ポインタ(アドレス)**, 「何が」という情報を**値**と言います. ここで, 「どこまで」という情報を決めるのが値の種類, 型です. そのために静的型付けでないと「どこまで」を決定できなくなってしまいます.

Pythonでは値だけを見ていましたが, C/C++ではポインタが追加されます. これは何のメリットがあるのでしょうか? 一番のメリットはコピー回数削減です. あるデータを何度も更新したいとき, 更新のたびに新たな領域にコピーするのは効率が悪いです. ポインタを固定し, そこに書いてある値のみを更新していく方が空間的な効率がいいです.

## アドレスとポインタ
この二つはよく似ていますが, ちょっとだけ違います. どのくらい違うかというと, 整数$\mathbb{Z}$と整数型`int`くらい違います.

アドレスというのはメモリの番地のことです. 配列の添え字と同じ概念です. C/C++ではメモリを1バイトごとに切り分け, それぞれにアドレスが与えられています. (たいていの場合, アドレスというと1アドレス=1バイトですが, たまにそうではないプログラミング言語もあります.)

ポインタというのは`int`などと同じ「型」です. ポインタ型とも言い, **アドレスを扱う型**です. ここで重要なのが, **アドレスを知りたいかどうか**です.

いま, メモリのどこかに`10`という値を格納したとします. このとき実はコンパイラがそのアドレスを記憶します. なのでプログラマがアドレスを知らなくても, この値を紛失することはありません. **プログラマが**アドレスを扱いたいと思ったときのみ, アドレスを記憶します. このときアドレスを扱うプログラミング上のデータ型をポインタと言います. メモリを配列に例えると, `10`という値があり, 別のところには`10`を格納した添え字を意味するアドレスが格納されていて, `[10, , , ,0 , , , ...]`のようになっているイメージです. したがってポインタもアドレスを持ち, それをポインタとして扱うことができます. これを二重ポインタといいます. 配列で言うと `[10, , , ,0 , ,4 , , , ...]` のようになります.

逆に, 値が無いのにアドレスを先にポインタとして定義できます. これをnullポインタと言います. nullポインタは値が無いのではなく, そもそも(有効な)アドレスを持ちません.

## 値→ポインタ, ポインタ→値
```cpp
int n = 10;
```
と書けば, `n`は「値」として定義され, `10`で初期化されます. 簡単ですね. さて, この`n`はどこにあるか知りたいとします. そういう時は,`&n`と書きます.
```cpp
#include <iostream>
int main(void){
    int n = 10;
    std::cout << &n << std::endl;
}
```
これを実行すると,
```cpp
0xfffff6ea425c
```
のようにアドレスが得られます. この`&`は値を入力するとポインタを返す関数のように機能します. 

逆に, あるポインタ`p`に格納されている値を知りたい場合は`*p`と書きます. つまり`*`は`&`の逆関数のように機能します.
```cpp
#include <iostream>
int main(void){
    int n = 10;
    std::cout << *&n << std::endl;
}
```
これを実行すると,
```cpp
10
```
となります.

## ポインタ型 `int*`
関数`&`でreturnされるのはintのポインタ型`int*`です. どこから「どこまで」という領域を決定するために, ポインタ型は値の型ごとに作られます. `&n`の戻り値を保持するためには`int* p = &n`と書きます.
```cpp
#include <iostream>
int main(void){
    int n = 10;
    int* p = &n;
    std::cout << p << std::endl;
}
```
```
0xffffc10a7b6c
```

関数としては, `*`という記号は値を返すのですが, `int*`という型は値ではなくポインタです. 値の意味する型は普通に`int`です. ちょっとややこしいですが慣れてください.

## 値渡しとポインタ渡し
```cpp
#include <iostream>

void func1(int n){
    std::cout << &n << std::endl;
}

void func2(int* n){
    std::cout << n << std::endl;
}

int main(void){
    int n = 10;
    std::cout << &n << std::endl;
    func1(n);
    func2(&n);
}
```
```
0xffffc7288c28
0xffffc7288c2c
0xffffc7288c28
```

`func1()`は引数が`int`で, `func2()`は引数が`int*`です. `func1()`には値が渡され, これを「**値渡し**」といいます. `func2()`にはポインタが渡され, これを「**ポインタ渡し**」と言います. 出力を見てみると, 値渡しでは別のメモリ領域が新たに確保され, ポインタ渡しでは元のアドレスが出力されていることが分かります. 値渡しでは空間的な効率が悪いので, もとのデータを保持しておく必要がないならばポインタ渡しの方が効率がいいです.

## 参照型 `int&`

ポインタ渡しにはいくつかの問題があります:
- null渡し.
- 二重解放/解放忘れ.
- ポインタ算術によるスタック破壊.

このような問題点が指摘され, 「ポインタ渡しより安全で, 値渡しよりも効率的」な渡し方が求められ, **参照**という概念が導入されました.

```cpp
#include <iostream>
int main(void){
    int n = 10;
    int& r = n;
    std::cout << r << std::endl;
}
```
```
10
```

これはポインタ型のコードにおける`int*`を`int&`にしたものです. 結果がアドレスから値になりました. この`r`は`n`のコピーではなく, `n`の「**参照**」といい, **`n`の分身のように振る舞います.** `n`を更新すると`r`も更新されます.
```cpp
#include <iostream>
int main(void){
    int n = 10;
    int& r = n;
    n++;
    std::cout << r << std::endl;
}
```
```
11
```
逆に参照`r`を更新することで`n`を更新することもできます.
```cpp
#include <iostream>
int main(void){
    int n = 10;
    int& r = n;
    r++;
    std::cout << n << std::endl;
}

```
```
11
```

## 参照渡し
```cpp
#include <iostream>

void func3(int& r){
    std::cout << r << std::endl;
    std::cout << &r << std::endl;
}

int main(void){
    int n = 10;
    int& r = n; // nを参照する変数rを定義
    std::cout << &n << std::endl;
    func3(r);
}
```
```
0xffffcd727eec
10
0xffffcd727eec
```
同じポインタを指す何かを渡していることが分かります.


## 参照という概念: 名前と実体の分離
目の前に犬がいます. 名前をポチとしましょう. この犬があなたの元を離れて遠くへ行き, 他の誰かにFidoと名付けられたとします. 一匹の犬に二つの名前が付けられました. ポチの飼い主とFidoの飼い主が死に, ポチ/Fidoはその名を呼ぶ者がいなくなりました. 一匹の犬に0個の名前が付けられました. 名前を失っても, 犬は元気に生きています.

人間は名前を介して事物を了解しますが, **名前は実体ではありません**. 名前とは人間が認識やコミュニケーションのために適当に張り付けたものに過ぎず, 剥がすことも複数貼り付けることも可能な都合のいいものです. 

メモリ上のデータはすべてアドレスによって指定できるので, アドレスを名前に使えばいいのですが, それは人間の脳に厳しすぎます. そこで, メモリ上のビット列に自由に名前をつけることが許されました. それが, 変数です. これは人間のために行ってるサービスに過ぎず, コンパイル時にすべてのデータは名前を失います.

「参照」という機能は, 一つのデータに複数の名前を付けることを許可するというものに他ありません. ゆえに, しばしば**参照とは別名である**と言われます. 

馴染みのない言葉を使って「値渡し」「参照渡し」と言うからややこしくなるのであって, 「実体渡し」「名前渡し」と言えば明確に違いがわかると思います. 「ポインタ渡し」は「場所渡し」と言えるでしょうか.

## 名前のコピーと実体のコピー
学習を進めると「コピー」というものには二種類あり, それぞれ「シャロ―コピー(浅いコピー)」「ディープコピー(深いコピー)」と呼ばれていると知ることになります. この二つの違いがわからないというのがよくあるつまづきポイントなのですが, 結局のところ名前と実体を混同しているのが理解を妨げる要因となります.

そもそも, シャロ―コピー/ディープコピーの違いが問題となるのはクラスや構造体, あるいは配列など, 複数の要素からなる集合体をコピーするとき, さらにその要素として他の変数の参照を持っているときのみとなります. ちょうど「名簿」のようなものをコピーするときのみ, その深度が問題になります.

名簿そのもの, つまり名前をコピーすることをシャロ―コピーと言います. 普通, 名簿をコピーすると言えばこっちを想像すると思います. 参照型のコピーもシャロ―コピーです.

一方, 名前と, その名前に付随する実体を同時にコピーすることをディープコピーといいます. 


## まとめ
### 単項演算子として
`*n` → 値
`&n` → ポインタ

### 型宣言として
`int*` → ポインタ型
`int&` → 参照型

## 予習
こう見ると参照がとても便利で使いやすそうですが, 実際は欠点も知られています. オブジェクト指向において, クラスや構造体を作ったとします. 自作のクラスや構造体に対しても`*`や`&`をつけることができます. オブジェクト指向ではコンストラクタやデストラクタでオブジェクトの生成や破壊を行いますが, このとき, 自身を参照している変数があるにもかかわらず, 自身を破壊すると**ダングリング参照**というエラーが起きます.

そこで, **スマートポインタ**という機能が実装されました.

## 補足: 容量が定まらないデータの格納

配列や文字列のように, コンパイル時に必要な容量が定まらず, どのくらいメモリ領域を確保すればいいのかわからない情報もあります. これらはどのようにしてメモリに格納するのでしょうか?

C++では`std::vector`や`std::string`も他の型と同様にメモリ(ヒープ領域)の**連続する領域**に格納されます. たとえ必要な空き容量が不明だとしても, データはメモリ上で散り散りに配置しないというのが現在のC++の仕様です.

空の配列を宣言するとまず容量0のデータ領域が確保され, 要素を追加していくごとに確保する容量が増えていきます. 今の仕様では, 容量が足りなくなると容量を(多くの場合)2倍にしていきます.

この仕様は次のような問題を引き起こすことが容易に想像できます: 「データを追加していくと, いつか連続する空き領域が足りなくなる」. この現象が起きると, C++では別のより広い空き領域にデータを移動します. そうです, 古いポインタが無効になります. 救済処置があるとかではなく, 普通にエラーが起きます. 特にオブジェクトの生成と破壊を繰り返すとメモリ上でフラグメンテーションが起き, この現象のよる弊害が顕著になります. そのため配列のサイズはできるだけ先に宣言し, サイズが不明の配列を扱う際は慎重に実装した方がいいです. (現在のメモリアロケータはこの問題が起きないように色々工夫しているらしいので, そこまで気にしなくていいかもしれない)