はじめに
この記事は普段生ポインタを使ってコードを書いていた自分がスマートポインタに乗り換えるために勉強したことをまとめたものです。
間違いがあればコメントなどで教えていただけると幸いです。
スマートポインタ
C++で動的にヒープ領域中のリソースを確保する方法の1つとして生ポインタとnew
演算子を使うものがあります。
int main() {
int *p = new int[10000]; // メモリを確保
memset(p, 2, 10000);
for (int i = 0; i < 10000; ++i) {
cout << p[i] << endl;
}
delete[] p; // 解放
}
非常にオーソドックスな方法ですが、new
後はdelete
でリソースを解放する必要があります。
C++ではJavaなどで採用されているGCの仕組みはないため、コードを書く人が責任を持ってメモリを解放しなければいけません。これを忘れるとメモリリークの原因になります。
C++11以降ではスマートポインタを使うことでリソースの解放をプログラムに任せられるようになりました。
スマートポインタは、動的に割り当てられたオブジェクトへのポインタを保持し、スマートポインタが所属するスコープを抜けると自動的にリソースが解放されます。
スマートポインタは標準ライブラリでは4種類用意されています。
スマートポインタ | |
---|---|
std::unique_ptr | 指定したヒープ上のリソースへの所有権を唯一持つポインタ。 |
std::shared_ptr | 1つのヒープ上のリソースを複数のオブジェクトが共有できるポインタ。 |
std::weak_ptr | shared_ptrを監視するポインタ。shard_ptrによる循環参照を防ぐ。 |
std::auto_ptr | C++11以前のスマートポインタ。コピーによって所有権が移ってしまう。C++11で非推奨、C++17で削除。 |
本記事ではunique_ptr
についてまとめます。
unique_ptrの基本的な使い方
unique_ptrとは
unique_ptrはリソースへの所有権をただ1つ持つポインタです。unique_ptrが参照しているポインタをコピーしようとするとエラーが発生します。もし新たなポインタにunique_ptrが持つリソースを参照させたいときは、後述するムーブまたはムーブコンストラクタを使用して明示的にリソースの所有権を譲渡してもらう必要があります。
コンストラクタ
unique_ptrの宣言方法は主に4つです。
std::unique_ptr<int> up1; // (1)空のユニークポインタを宣言する
int *p = new int;
std::unique_ptr<int> up21(p); // (2)生ポインタの所有権を受け取る
std::unique_ptr<int> up22(new int); // こう書いても良い
std::unique_ptr<int> xp3(new int);
std::unique_ptr<int> up3(std::move(xp3)); // (3)std::moveで他のユニークポインタが所有するポインタを受け取る
up3 = std::move(xp3); // これでも良い(ムーブ代入)
std::unique_ptr<int> up4 = nullptr; // (4)nullptrで初期化
もちろんテンプレートにはint
以外の型を指定することもできます。
またT[]
型もunique_ptrで宣言することができます。
std::unique_ptr<int[]> up5(new int[10]); // (5)int[10]を持つユニークポインタ
C++14以降ではヘルパー関数 std::make_unique
を使ってunique_ptrを構築できます。
// T型 引数が要素になる
std::unique_ptr<int> up6 = std::make_unique<int>(5); // (6)cout << *up6 << endl; // 5 となるユニークポインタを構築
// T[]型 引数の要素数を持つ配列になる
std::unique_ptr<int[]> up7 = std::make_unique<int[]>(10); // (7)要素数10のint[]を構築
unique_ptrへのアクセス
unique_ptr<T>
ではoperator*, operator->
が定義されているので、生ポインタと同じようにリソースにアクセスできます。
std::unique_ptr<int> up = std::make_unique<int>(5);
std::cout << *up << "\n"; // 5
std::unique_ptr<std::vector<int>> uv = std::make_unique<std::vector<int>>(10);
uv->at(2) = 1;
std::cout << uv->at(2) << "\n"; // 1
unique_ptr<T[]>
ではoperator[]
が定義されているため、[]
によるアクセスも可能です。
std::unique_ptr<char[]> up = std::make_unique<char[]>(10);
up[5] = static_cast<char>(97);
std::cout << up[5] << "\n"; // 'a'
リソースの解放
明示的なリソースの解放は reset
で行います。
std::unique_ptr<char[]> up = std::make_unique<char[]>(10);
up.reset(); // リソースの解放
up.reset(new char[5]); // リソースを解放して、新たに要素数5のchar[]を所有させる。
生ポインタの取得(get, release)
どうしてもunique_ptrから生ポインタが必要になった場合(CのAPIを呼ぶ場合など)にはget
, release
を使用します。
ただし、get, releaseには次のような違いがあります。
メソッド | 説明 |
---|---|
get | 生のポインタを取得する。unique_ptrは所有権を手放さない。 |
release | 生のポインタを取得する。unique_ptrは所有権を手放す。 |
get
ではunique_ptrは所有権を手放さないので、もし生ポインタdeleteしてしまうと二重でポインタを解放してしまうことになります。
release
では所有権がなくなるので、しっかりポインタを解放する必要があります。
#include <iostream>
#include <memory>
using namespace std;
int main() {
unique_ptr<int> p(new int(2));
int* t = p.get();
*t = 1;
cout << *t << endl;
delete t;
// *** Error in `./prog.exe': double free or corruption (fasttop): 0x00000000019bf1b0 ***
}
#include <iostream>
#include <memory>
using namespace std;
int main() {
unique_ptr<int> p(new int(2));
int* t = p.release();
*t = 1;
cout << *t << endl;
delete t; // ok(リソースの解放はコーダーに委ねられる)
}
swap
2つのunique_ptrの所有しているリソースを入れ替えることができます。
std::unique_ptr<int> up1(new int(5));
std::unique_ptr<int> up2(new int(3));
std::swap(up1, up2);
std::cout << *up1 << "\n"; // 3
std::cout << *up2 << "\n"; // 5
他にも
自分は完全に理解できていませんが、他にもデリータを返すget_deleter
や多数の演算子が定義されています。
より詳細な解説は人類の叡智が詰まった以下のサイトをどうぞ。
#まとめ
- リソースを自動的に解放してくれるスマートポインタ。種類はunique_ptr, shared_ptr, weak_ptr, auto_ptrの4つ。(1つは非推奨)
- ほぼ生ポインタのように操作できる。
- もし生ポインタが必要になったら, get, release。ただしリソースの所有権はだれが持っているかを忘れないこと。
参考文献
挙動を試すのにいっぱい使わせてもらいました。
Wandbox