SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Modern C++入門

生のポインタは使わないで! Modern C++のメモリ管理

第3回 ポインタはスマートポインタで!

スマートポインタを初期化する

 まずは、初期化を見ていきましょう。unique_ptrやshared_ptrはテンプレート型のクラスとして実装されているので、そのインスタンスを生成して使うことになります。テンプレートについては、第2回で簡単に紹介しましたね。

スマートポインタの宣言[C++11]

 スマートポインタは、unique_ptrなどのテンプレート型変数として宣言します。例えば、こんな感じです。

リスト init.cpp
#include <memory>		(1)
using namespace std;		(2)
…略…
// デフォルトコンストラクタで初期化する(nullptrが入る)
unique_ptr<int> u_int_ptr;    // int型のunique_ptr
shared_ptr<string> s_str_ptr;    // string型のshared_ptr

 スマートポインタ(unique_ptrなどのクラス)の利用には、(1)のinclude文が必要です。また、クラスはstd名前空間に属するので、スコープ解決演算子(std::)を省略するために(2)のusing宣言を定義しています。

 記述の通りunique_ptrもshared_ptrもクラスなので、型引数intおよびstringを伴ったインスタンスの生成であることは変わりません。このとき、デフォルトコンストラクタが呼ばれて安全に初期化されます。やっていることは、nullptr(0をdefineしたとかのNULLではなく、言語仕様でキッチリ決められたnull)を保持するということだけです。よって、生のポインタを初期値なしで宣言したときのような漏れはないのですが、nullptrであることには変わらないので、依然として不安な感じは残りますね。どうしましょうか。

初期値を与えてスマートポインタを宣言[C++11]

 unique_ptr<T>やshared_ptr<T>のコンストラクタに引数を与えることによって、スマートポインタの初期値を指定することができます。

リスト init.cpp
// コンストラクタに引数を与えて初期化する(初期値が入る)
unique_ptr<int> u_int_ptr2(new int(100));    // int型のunique_ptr
shared_ptr<string> s_str_ptr2(new string("Hello"));    // string型のshared_ptr

 コンストラクタの引数は、new演算子によって確保された動的なメモリ領域です。直値としての100、"Hello"などは指定できません。これは、スマートポインタはあくまでもメモリ管理のための構造なので、当たり前ですね。このときは、先の例とは違ってnullptrでない有効なメモリ領域のアドレスを保持します。100という整数が格納されているメモリ領域、そして"Hello"を値として持つstringが格納されているメモリ領域です。これで、上の例の不安はなくなります。よかったですね。

reset関数によるメモリ領域の割り当て[C++11]

 最初の例のように、インスタンスを作成だけしておき、あとからメモリを割り当てることもできます。これは「所有権の委譲」といい、reset関数を使います。初期値のないnullptrのママのスマートポインタに、後からメモリを割り当てるというときに使います。

リスト init.cpp
// あとからメモリ領域を割り当てる
u_int_ptr.reset(new int(200));
s_str_ptr.reset(new string("World"));

 もし、u_int_ptrなどにすでにメモリ領域が割り当てられていたら、それは自動的に解放されます。所有されないメモリ領域が解放漏れで残ってしまう心配はないのです。安心ですね。

make_unique<T>/make_shared<T>関数による初期化[C++11][C++14]

 ところで、ここまではメモリ領域の生成にnew演算子を使ってきました。おいおい、newは使わないで!じゃないのかい?やはりnewを使うというんじゃ、やっぱりdeleteも使うわ~なんてことにならないだろうね?という声があったかどうかは知りませんが、インスタンスの生成とメモリ領域の確保を一度にやってくれる便利な関数があります。それが、make_unique<T>関数とmake_shared<T>関数です。

リスト init.cpp
// インスタンス生成とメモリ領域の割り当てを一度に行う
auto u_int_ptr3 = make_unique<int>(200);                  // C++14以降
auto s_str_ptr3 = make_shared<string>("Hello, World!!");

 これは便利ですね。型推論が働くので宣言文がすごくスッキリします。型推論って何?という読者は第2回を読んでくださいね。これ、下手すると左辺にも右辺と同じ型名を書かなければなりません。autoって、なんて便利なんでしょう! なお、make_unique<T>関数はC++14以降でないとサポートされません。処理が複雑そうなmake_shared<T>関数の方じゃなくて?と思いますよね。 なお、nullptrのママのスマートポインタを生成したり、newを使うことがトラブルのもととなりうるので、特に理由がない限りmake_unique<T>関数とmake_shared<T>関数を使うことをおすすめしますね、アタシは。

[NOTE]make_shared関数を使うメリット

 shared_ptrは、複数の所有権を管理できるという機能のために、値のためのメモリ領域の他に、参照カウンタのためのメモリ領域も確保、操作します。このため、何をするにもunique_ptr関数に比べると処理に時間がかかります。ですがmake_shared<T>関数を使うと、インスタンス初期化とメモリ領域の割り当てを一度に行ってくれるので、それぞれを別個に行うよりも速度上のメリットが発生します。なおさらこっちを使うべきですね。

 make_unique<T>関数とmake_shared<T>関数は、Tのコンストラクタが複数の値をとる場合にも利用できます。例えば、タプルであるstd::tuple<T, ...>クラスのスマートポインタを利用したい場合、以下のようになります。

リスト init.cpp
#include <tuple> // タプルの利用に必要
 …略…
 // 複数の引数を持つコンストラクタを呼び出す
 auto u_tuple_ptr = make_unique<tuple<int, int, string>>(200, 10, "hello"); // C++14以降
 auto s_tuple_ptr = make_shared<tuple<int, int, string>>(1000, 20, "world");

 参考までに、タプルの要素へのアクセスは、std::get関数をget<0>(*u_tuple_ptr)のように呼び出します(この場合は0番目の要素)。u_tuple_ptrに「*」が付いてるけど、これってアレかな?という疑問には次節でお答えしますね。

[NOTE]make_unique_for_overwrite関数とmake_shared_for_overwrite関数

 C++20以降では、make_unique_for_overwrite<T>関数とmake_shared_for_overwrite<T>関数が使えるようになりました。これらの関数は、make_unique<T>関数とmake_shared<T>関数のような値による初期化を行いません。つまり、あとで初期値を設定したりすることが前提の関数と言えます。これは、どうせ値を後で設定するのなら、わざわざ初期化するのはムダじゃね?ということもあってできた関数のようです。しかし、安全のために初期化しているのに、それをムダと言われるばかりか、値の初期化をプログラマに委ねるような昨今の状況に逆行するようなものなので、これらの利用は慎重になるべきでしょう。コンパイラ処理系によっては使用できないものもあるようです。

配列の利用[C++11][C++14][C++17][C++20]

 スマートポインタを使うのはどのようなときでしょうか? そもそも、メモリを動的に確保したいというときは大きいデータを扱いたいときでしょうから、当然配列もその対象に入ってくるでしょう。スマートポインタで配列を使うのは簡単で、型パラメータをT[]のようにブラケット付きにするだけです。ただし、リスト中のコメントのように利用可能なバージョンが異なるので注意してください。もちろん、make_unique<T>関数などを使う方法が推奨されるのは同じです。

リスト array.cpp
// 「型名[]」をテンプレート引数に指定してコンストラクタを呼ぶ
unique_ptr<int[]> u_int_array_ptr(new int[10]);
shared_ptr<string[]> s_str_array_ptr(new string[10]);   //C++17以降

// make_unique<T>関数などを使う
auto u_int_array_ptr2 = make_unique<int[]>(10);   //C++14以降
auto s_str_arrau_ptr2 = make_shared<string[]>(10);   //C++20以降

次のページ
メモリ領域へアクセスする

この記事は参考になりましたか?

Modern C++入門連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 山内 直(WINGSプロジェクト ヤマウチ ナオ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook <個人紹介>WINGSプロジェクト所属のテクニカルライター。出版社を経てフリーランスとして独立。ライター、エディター、デベロッパー、講師業に従事。屋号は「たまデジ。」。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/18446 2023/10/20 11:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー