こんにちは。
C++を初めて触ったばかりの時に苦しめられた記憶のあるポインタ。
これを安全かつ便利に使うことができるのがスマートポインタなのですが、
学生自体に思いの外詰まってしまったことを思い出したため、復習も含め、書き出してみました。
ポインタとは
まず、大前提としてポインタについて復習しましょう。
ポインタとは文字や変数などの情報が存在する場所(アドレス)を指し示してくれる変数です。
今回はスマートポインタの説明になるため、詳しくは記載しませんが、
「*」をつけてポインタ変数を宣言したり、
「&」をつけてアドレスを格納したりする。
というものです。
スマートポインタとは
さて、では本題のスマートポインタについてです。
C++のスマートポインタには下記のような種類が存在します。
(std::は省略)
unique_ptr
shared_ptr
weak_ptr
これらには各々特徴が存在し、用途によって使い分けされます。
また、この3つのスマートポインタの共通点として、RAIIによって自動でメモリを解放してくれるというものがあります。
unique_ptrについて
unique_ptrは1つのオブジェクトに対する所有権を一つだけに制限することができるポインタで、コピーすることが出来ない代わりに、メモリの消費量が少なく、高速で処理ができるのが特徴です。
shared_ptrについて
shared_ptrは複数のポインタで同じオブジェクトの所有権を共有できるポインタです。
unique_ptrがオブジェクトを1つにするのに対して、こちらは1つのオブジェクトを共有、参照できるのが特徴です。
ただし、オブジェクトを共有しすぎて破棄のタイミングがわからなくなったり、
参照カウンタが増加することにより発生する、循環参照が発生しないように注意が必要です。
循環参照についてはweak_ptrと共にご説明します。
weak_ptrについて
参照カウンタを増やすこと無くshared_ptrが管理するオブジェクトを参照することができるポインタです。
参照カウンタとはざっくり言うと、shared_ptrが管理する、オブジェクトの所有者の数のことです。
この参照カウンタが0になることで、スマートポインタは「使用されていない」と断定し、自動で解放処理を行ってくれるわけです。
では、なぜそんな参照カウンタを増やさないようにする変数が必要なのでしょうか。
その理由は循環参照と呼ばれるバグにあります。
循環参照とはshared_ptr同士がお互いを指し示してしまうことであり、
前述の通り参照カウンタが0にならないと解放されないスマートポインタでこれが発生すると、永久にポインタが解放されなくなってしまうのです。
長くなりましたが、weak_ptrはそんなshared_ptrの補助を行うようなポインタとなります。
小話
以降は小話となります。
ここまで呼んでいただいた方からすれば、「ならポインタは全てスマートポインタに置き換えればいいじゃないか。」とお思いの方もいると思います。
しかし、実は何でもかんでもスマートポインタを用いたら良いというわけではないのです。
それはなぜか。
そこには型の大きさが関係しています。
スマートポインタのサイズは64bit環境では8バイトや16バイトとされており、今回例として挙げた
unique_ptr は8バイト
shared_ptrとweak_ptr は16バイト
となっています。
例えば多くの環境で4バイトとして存在しているint型のスマートポインタを宣言するとします。
auto value = std::make_unique<int>(5);
上記の処理は一見何の変哲もない処理にみえるのですが、実は処理の手間が増えてしまっているのがわかるでしょうか。
元々は4バイトであるint型を8バイトであるunique_ptrで管理してしまうと、メモリの使用量や、ヒープ確保のコストが発生してしまうのです。
勿論、int型をスマートポインタで持っていけないというわけではなく、オブジェクトに寿命管理が必要な場合には使用するとよいでしょう。
以上
本記事は、業務や学習を通して理解した内容を、自分自身の整理も兼ねてまとめたものです。内容に改善点や補足などがありましたら、ご指摘いただけますと幸いです。
以下は本記事を書くに当たり、私が検索した単語になります。
・RAIIとは
Resource Acquisition Is Initialization(リソースの取得は初期化である)の略
オブジェクトが作られたらリソースを取得し、オブジェクトが消えたら自動で解放する仕組み
・ヒープとは
newを用いて確保する領域の事。
int i = 0;のように確保するのはスタック。
・オーバーヘッド
本来必要な処理以外に発生する余分なコストのこと。