LoginSignup
53
52

More than 3 years have passed since last update.

初学者が学ぶ生ポインタとunique_ptrの違い、操作方法

Last updated at Posted at 2019-06-27

はじめに

この記事は普段生ポインタを使ってコードを書いていた自分がスマートポインタに乗り換えるために勉強したことをまとめたものです。
間違いがあればコメントなどで教えていただけると幸いです。

スマートポインタ

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つです。

constructor
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で宣言することができます。

constructor
std::unique_ptr<int[]> up5(new int[10]); // (5)int[10]を持つユニークポインタ

C++14以降ではヘルパー関数 std::make_uniqueを使ってunique_ptrを構築できます。

make_unique
// 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->が定義されているので、生ポインタと同じようにリソースにアクセスできます。

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[]が定義されているため、[]によるアクセスも可能です。

operator[]
std::unique_ptr<char[]> up = std::make_unique<char[]>(10);
up[5] = static_cast<char>(97);
std::cout << up[5] << "\n"; // 'a'

リソースの解放

明示的なリソースの解放は reset で行います。

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では所有権がなくなるので、しっかりポインタを解放する必要があります。

get
#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 ***
}
release
#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や多数の演算子が定義されています。
より詳細な解説は人類の叡智が詰まった以下のサイトをどうぞ。

cpprefjp
cppreference.com

まとめ

  • リソースを自動的に解放してくれるスマートポインタ。種類はunique_ptr, shared_ptr, weak_ptr, auto_ptrの4つ。(1つは非推奨)
  • ほぼ生ポインタのように操作できる。
  • もし生ポインタが必要になったら, get, release。ただしリソースの所有権はだれが持っているかを忘れないこと。

参考文献

C++11スマートポインタ入門

挙動を試すのにいっぱい使わせてもらいました。
Wandbox

53
52
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
53
52