導入
C++開発において、リソース管理を自動化する「スマートポインタ」は欠かせないツールです。しかし、便利な反面、それぞれのスマートポインタがメモリ上でどれだけのサイズを占有しているかを意識しているでしょうか。特に組み込みシステムや、数百万個単位のオブジェクトを生成するような大規模システムでは、このわずかなメモリ消費の差がパフォーマンスに直結します。今回は、std::unique_ptrとstd::shared_ptrのサイズの違いと、その理由を解説します。
基礎知識
まず、スマートポインタは「動的に確保したメモリを所有し、不要になったら自動で解放する」ためのラッパークラスです。
std::unique_ptrは、単一の所有権を持つポインタです。余計な管理データを持たないため、基本的には生ポインタと同じサイズ(64bit環境では8バイト)になります。
一方、std::shared_ptrは、複数の箇所から所有権を共有できるポインタです。どのオブジェクトが何個のshared_ptrから参照されているかをカウントするための「制御ブロック」へのポインタを内部に保持する必要があるため、unique_ptrの2倍のサイズ(16バイト)になることが一般的です。
実装と解決策
メモリ使用量を抑えたい場合は、可能な限りstd::unique_ptrを選択するのが鉄則です。std::shared_ptrは非常に強力ですが、参照カウントの制御にはアトミック操作などのコストも伴います。
「所有権を共有する必要があるか」を設計段階で見極めることで、不要なメモリ消費と実行時のオーバーヘッドを回避できます。
サンプルプログラム
以下のコードで、実際に実行環境でのサイズを確認してみましょう。
include <iostream>
include <memory>
int main() {
// std::unique_ptrのサイズを確認
// 内部的には生ポインタ1つ分のみを保持するため、64bit環境では8バイト
std::cout << "unique_ptrのサイズ: " << sizeof(std::unique_ptr<int>) << " バイト" << std::endl;
// std::shared_ptrのサイズを確認
// オブジェクトへのポインタと、制御ブロックへのポインタの2つを保持するため、16バイト
std::cout << "shared_ptrのサイズ: " << sizeof(std::shared_ptr<int>) << " バイト" << std::endl;
return 0;
}
応用・注意点
現場で陥りやすい罠として、「とりあえず全部shared_ptrにしておく」という実装があります。これはコードの可読性は維持できますが、メモリ不足やキャッシュ効率の低下を招く原因となります。
また、カスタムデリータを指定した場合、unique_ptrのサイズは変化する可能性があります(空のクラスであれば最適化されますが、ステートを持つデリータの場合はその分サイズが増えます)。
まずはunique_ptrを使い、どうしても共有が必要になった時だけshared_ptrに切り替える「unique_ptrファースト」の考え方を持つことが、堅牢で効率的なC++コードを書くコツです。

コメント