ダウンロード サンプルファイル (4.3 KB)
スマートポインタとムーブセマンティクス
ここまでコピー、ムーブと扱ってきましたが、第3回で紹介したスマートポインタは、このムーブを実装したクラスです。ここからは、スマートポインタをムーブセマンティクスという観点でおさらいしてみます。
所有権のコピーを認めないスマートポインタ[C++14]
最初は、所有権のコピーを認めないスマートポインタunique_ptrです。所有権、覚えていますか?値の保持や変更を許される代わりに、メモリ領域の管理責任を負うという仕組みでした。このことから、所有権とは値そのもの、メモリ領域そのものとも言えると筆者は思っています。
スマートポインタunique_ptrは、初期値込みでnew演算子も使わずに安全に初期化できるstd::make_unique<T>関数を使って宣言するのでした。make_unique<T>関数は、文字通り所有権を保有するインスタンスを1個しか認めないスマートポインタを生成します。unique_ptr自体はC++ 11から利用可能ですが、make_unique<T>関数はC++ 14からのサポートです。
auto u_str_ptr = make_unique<string>("Hello, World!!");
これで、所有権と一対一となるunique_ptrというスマートポインタが出来上がります。型はstringで、値は"Hello, World!!"です。このとき、u_str_ptrは"Hello, World!!"が置かれたメモリ領域を「所有」します。所有しているものは、いつかは手放すときが来ます。いつ来るかというと、通常それはスコープを外れたときです。一般的には、ブロックや関数の出口となります。所有権の喪失とともに、所有していたメモリ領域が自動で解放されます。
{
auto u_str_ptr = make_unique<string>("Hello, World!!");
cout << *u_str_ptr << endl;
…ブロックの出口で所有権を失う。メモリ領域も解放される
}
では、このブロック内で別のスマートポインタを宣言して、初期値をu_str_ptrにしてみます。代入なので、所有権がコピーされておのおのが異なるメモリ領域を保持するか、あるいは所有権を共有するかのような動きをするように見えますが、実際はコンパイルエラーとなります。それは、unique_ptrがコピーコンストラクタもコピー代入演算子も持っておらず、所有権のコピーを認めていないからです。
{
auto u_str_ptr = make_unique<string>("Hello, World!!");
auto u_str_ptr2 = u_str_ptr; // 代入はコンパイルエラー
}
スマートポインタunique_ptrにとって、所有権はムーブの対象です。所有権をムーブするには、紹介済みのstd::move関数を使います。move関数によってu_str_ptrが右辺値にキャストされるので、所有権がu_str_ptrからu_str_ptr2にムーブします。u_str_ptrはnullptrとなり、表示させようとするとsegmentation faultなどのエラーとなります。その替わり、新たに所有権を得たu_str_ptr2の表示は問題なく行われます。
{
auto u_str_ptr = make_unique<string>("Hello, World!!");
auto u_str_ptr2 = std::move(u_str_ptr);
cout << *u_str_ptr2 << endl;
cout << *u_str_ptr << endl; // segmentation fault
}
所有権を共有できるスマートポインタ[C++11]
スマートポインタには、所有権を共有できるshared_ptrがありました。std::make_shared<T>関数で作成します。あくまでも共有で、メモリ領域を複製するわけではありません。
auto s_str_ptr = make_shared<string>("Hello, World!!");
auto s_str_ptr2 = s_str_ptr;
cout << *s_str_ptr << endl << *s_str_ptr2 << endl; // 問題なく2つ表示される
*s_str_ptr = "Hello, C++ World!!";
cout << *s_str_ptr << endl << *s_str_ptr2 << endl; // 双方とも書き換わる
smartptr.cppでは代入時にコンパイルエラーとなっていましたが、shared_ptrでは問題なく表示まで実行されています。shared_ptrはコピーコンストラクタとコピー代入演算子を持っているので、代入によって所有権のコピーが実行され、s_str_ptrとs_str_ptr2が同じメモリ領域を所有するものとしてマークされます。片方を変更したら、もう一方も変更されます。
shared_ptrでも所有権はmove関数を使ってムーブできます。以下のリストでは、s_str_ptrの所有権をs_str_ptr3にムーブしています。ムーブ後でも、s_str_ptr2の所有権は生きていることに注目です。もちろん、s_str_ptrはムーブで無効になっているので表示させようとすると実行時にエラーとなります。
…上記の続き… auto s_str_ptr3 = std::move(s_str_ptr); cout << *s_str_ptr2 << endl << *s_str_ptr3 << endl; // 問題なく2つ表示される cout << *s_str_ptr << endl; // segmentation fault
共有スマートポインタは、簡単に言うと所有権を持っている変数(左辺値といった方がそれっぽいですね)が幾つあるのか、その情報を持っています。要はカウンタですね。カウンタが0以上である限りメモリ領域を保持し、0になればメモリ領域を解放します。このように、データ本体のためのメモリ領域とは別にカウンタの保持、操作が必要なので、若干メモリを食いますし、生成時や代入時の実行コストも少し増えます。
まとめ
今回は、Modern C++のキモとも言えるムーブセマンティクスを紹介しました。とっつきにくい用語ですが、ムーブという考え方をC++に導入し、コード実行の効率化を言語仕様として明確にものだというのをお伝えできたのではないかと思います。
次回は、いよいよ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編 」他、著書多数。
※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です
この記事は参考になりましたか?
この記事をシェア
