C++のstd::bad_allocとは?メモリ不足エラーの原因と対策を徹底解説
より具体的には、以下のような状況でstd::bad_alloc
が発生する可能性があります。
- メモリ不足
システムが利用可能なメモリを使い果たしている場合。これは、物理メモリが不足しているか、スワップ領域が使い果たされている場合に起こります。 - アドレス空間の枯渇
32ビットシステムなどで、プログラムが利用できる仮想アドレス空間を使い果たした場合。たとえ物理メモリが残っていても、プログラムに割り当てられるアドレスが不足していると、新たなメモリを確保できません。 - 非常に大きな確保要求
単一のメモリブロックとして確保しようとしているサイズが、システムが提供できる最大サイズをはるかに超えている場合。
発生する主な操作
std::bad_alloc
は主に以下の操作で発生します。
- 標準ライブラリのコンテナ
std::vector
、std::string
、std::map
などの標準ライブラリのコンテナは、要素を追加する際に内部的にメモリを確保します。このメモリ確保が失敗した場合も、内部でnew
が使われているため、std::bad_alloc
が送出されます。std::vector<int> myVector; try { myVector.reserve(1000000000ULL); // 非常に大きな容量を予約しようとする例 } catch (const std::bad_alloc& e) { std::cerr << "vectorのメモリ確保失敗: " << e.what() << std::endl; }
- new演算子
メモリを動的に確保する際に最も一般的に使用されるnew
演算子が失敗した場合に、この例外がスローされます。try { int* arr = new int[10000000000ULL]; // 非常に大きな配列を確保しようとする例 } catch (const std::bad_alloc& e) { std::cerr << "メモリ確保失敗: " << e.what() << std::endl; }
処理方法
std::bad_alloc
は、プログラムが予期せぬメモリ不足に陥ったことを示す重要なシグナルです。通常、この例外が発生した場合は、プログラムを正常に続行することが困難な状況にあることを意味します。
適切なエラーハンドリングを行うためには、try-catch
ブロックを使用してこの例外を捕捉し、以下のいずれかの対応を検討することが推奨されます。
- 処理の中断
例えば、一時的に必要となる大きなデータ構造の生成を諦めるなど、処理の一部を中断してプログラムの継続を試みる(ただし、これは状況によっては困難)。 - リソースの解放
可能な限りリソースを解放し、プログラムを安全に終了させる。 - エラーメッセージの表示
ユーザーにメモリ不足であることを通知し、プログラムを終了する。
new
演算子には、メモリ確保に失敗した場合に例外を送出する代わりにnullptr
を返すバージョンとしてnew (std::nothrow)
があります。
int* arr = new (std::nothrow) int[10000000000ULL];
if (arr == nullptr) {
std::cerr << "メモリ確保失敗 (nothrow)" << std::endl;
}
std::bad_alloc
はメモリ確保の失敗を示す例外であり、その根本原因は多岐にわたります。以下に主な原因とそれぞれのトラブルシューティング方法を挙げます。
メモリリーク (Memory Leak)
よくあるエラー
プログラムが動的に確保したメモリを適切に解放せず、時間とともに使用可能なメモリが枯渇していく状態です。特に長時間稼働するアプリケーションや、ループ内で大量のメモリを確保・解放を繰り返すようなコードで発生しやすいです。
トラブルシューティング
- プロファイラツールの使用
メモリリークを検出・特定するには、Valgrind (Linux)、Visual Leak Detector (Windows)、またはIDEに付属のメモリプロファイラ(Visual Studioの診断ツールなど)が非常に有効です。これらのツールは、解放されていないメモリブロックや、どこでメモリが確保されたかなどの情報を提供してくれます。 - スマートポインタの活用
std::unique_ptr
やstd::shared_ptr
などのスマートポインタを使用することで、RAII (Resource Acquisition Is Initialization) の原則に基づき、スコープを抜ける際に自動的にメモリが解放されるようになります。これにより、手動での解放忘れを防ぐことができます。// 悪い例: メモリリークの可能性 void func_bad() { MyClass* obj = new MyClass(); // ここで例外が発生するとdeleteが呼ばれない // delete obj; // 忘れがち } // 良い例: スマートポインタを使用 void func_good() { std::unique_ptr<MyClass> obj = std::make_unique<MyClass>(); // スコープを抜けるときに自動的にdeleteされる }
- delete / delete[] の忘れ
new
やnew[]
で確保したメモリは、必ず対応するdelete
やdelete[]
で解放する必要があります。- 確認点
各new
に対応するdelete
があるか。配列の場合はdelete[]
を使っているか。
- 確認点
巨大な単一ブロックの確保 (Huge Single Allocation)
よくあるエラー
利用可能な物理メモリや仮想メモリは十分にあるにもかかわらず、要求されたメモリブロックが連続した領域として確保できない場合に発生します。これは、特に巨大な配列やコンテナ(std::vector
など)を確保しようとしたときに起こりやすいです。
トラブルシューティング
- 64ビット環境への移行
32ビットアプリケーションは仮想アドレス空間が通常4GBに制限されます(OSによって異なる)。このうち、アプリケーションが使える領域はさらに限られるため、数GB程度のメモリ確保でもstd::bad_alloc
が発生することがあります。64ビットOS上でも32ビットアプリケーションとして動作している場合は、64ビットビルドに切り替えることで、はるかに広い仮想アドレス空間(理論上は18EB)を利用できるようになります。- 確認点
アプリケーションが32ビットと64ビットのどちらでビルドされているか。
- 確認点
- メモリマッピングの使用
非常に大きなファイルを扱う場合など、メモリマッピング (例:mmap
(Linux/macOS),MapViewOfFile
(Windows)) を利用して、ファイルの一部をメモリにマップすることで、物理メモリの制約を受けにくくする方法もあります。 - 設計の見直し
- 本当にそのサイズの連続したメモリが必要か再検討します。
- データを分割して扱う(例: 大きな2次元配列を
std::vector<std::vector<T>>
にするのではなく、std::vector<T>
を複数使う、あるいは自前でブロック管理を行う)。 std::vector
の代わりにstd::deque
の使用を検討する。std::deque
は内部的に複数の非連続なメモリブロックを使用するため、巨大な連続領域の確保が不要になります。
メモリの断片化 (Memory Fragmentation)
よくあるエラー
頻繁なメモリ確保と解放を繰り返すことで、利用可能なメモリが小さな断片に分かれてしまい、結果として総メモリ量は十分でも、要求されたサイズの連続したブロックが確保できなくなる現象です。
トラブルシューティング
- カスタムアロケータ
特定のデータ構造やオブジェクトに対して、専用のメモリ管理戦略を持つカスタムアロケータを使用することも検討できます。 - オブジェクトプールの使用
同じサイズのオブジェクトを頻繁に生成・破棄する場合、あらかじめ大きなメモリブロックを確保しておき、その中からオブジェクトを割り当てる「オブジェクトプール」を実装することで、断片化を軽減できます。
仮想メモリの枯渇 (Virtual Memory Exhaustion)
よくあるエラー
物理メモリが豊富にあっても、OSがアプリケーションに割り当てられる仮想メモリのアドレス空間が枯渇した場合に発生します。これは特に、32ビットシステムで顕著です。
トラブルシューティング
- スワップ領域の確認
スワップ(ページファイル)が不足している場合も、仮想メモリの確保が難しくなります。システムの空きスワップ領域を確認し、必要に応じて拡張を検討します。 - OSやコンパイラの設定
OSやコンパイラによっては、仮想メモリの使用方法に関する設定が存在する場合があります。例えば、Linuxのulimit
コマンドなどでプロセスの仮想メモリ上限を設定できます。 - 64ビットシステムへの移行
これが最も確実な解決策です。64ビットシステムでは、非常に広大な仮想アドレス空間が利用できます。
ヒープの破損 (Heap Corruption)
よくあるエラー
メモリの解放済み領域に書き込んだり(use-after-free)、配列の境界を越えて書き込んだり(buffer overflow)するなどの未定義動作が原因で、ヒープの内部構造が破壊され、その後にメモリ確保を試みた際にstd::bad_alloc
が発生することがあります。この場合、メモリの「量」が問題ではなく、「質」が問題となります。
- イテレータの無効化
std::vector
などのコンテナで要素の追加や削除を行った際、既存のイテレータやポインタが無効になることがあります。無効なイテレータを使用すると、ヒープの破損につながる可能性があります。イテレータの無効化ルールを理解し、適切に再取得するなどします。 - コードレビュー
ポインタの扱い、配列のインデックス、文字列操作など、メモリを直接操作する可能性のあるコードを注意深くレビューします。 - デバッグツール
- ValgrindやAddressSanitizer (ASan) などのメモリデバッグツールは、ヒープの破損や不正なメモリアクセスを検出するのに非常に強力です。
- デバッガ(GDB, Visual Studio Debuggerなど)のメモリビュー機能で、疑わしい領域のメモリ内容を確認します。
- std::set_new_handlerの利用
new
演算子がメモリ確保に失敗した際に呼び出されるカスタムハンドラを設定できます。これを利用して、エラーメッセージの出力や、緊急のメモリ解放処理などを試みることも可能です。ただし、最終的にメモリが確保できなければ例外がスローされるか、プログラムが終了します。 - 最小限のコードで再現
問題を切り分けるために、std::bad_alloc
が発生する最小限のコードを作成し、そのコードをデバッグすることで原因を特定しやすくなります。 - 再現性の確認
std::bad_alloc
は再現性が低い場合があるため、問題が特定のデータや操作で再現するかどうかを確認することが重要です。 - ログ出力の強化
std::bad_alloc
を捕捉した際に、発生箇所、要求サイズ、現在のメモリ使用量(OSのAPIで取得できる場合)などをログに出力することで、デバッグの手がかりを得られます。
new演算子によるstd::bad_allocの捕捉
最も基本的な例として、new
演算子で大量のメモリを確保しようとし、失敗した場合にstd::bad_alloc
を捕捉する例です。
#include <iostream> // std::cout, std::cerr
#include <new> // std::bad_alloc
#include <vector> // std::vector (内部でnewを使うため)
int main() {
// 非常に大きなサイズの配列を確保しようとする
// システムのメモリ状況によっては実際にbad_allocが発生しない場合もある
// その場合は、より大きな値を試すか、繰り返し実行してメモリを消費させる必要がある
const size_t HUGE_SIZE = 10000000000ULL; // 100億個のint (約40GB)
try {
std::cout << "Attempting to allocate a huge array..." << std::endl;
int* bigArray = new int[HUGE_SIZE];
std::cout << "Successfully allocated " << HUGE_SIZE << " integers." << std::endl;
// 確保に成功した場合は、メモリを解放するのを忘れない
delete[] bigArray;
std::cout << "Memory freed." << std::endl;
} catch (const std::bad_alloc& e) {
// bad_alloc例外を捕捉した場合
std::cerr << "Caught std::bad_alloc: " << e.what() << std::endl;
std::cerr << "Memory allocation failed. Exiting." << std::endl;
// プログラムの安全な終了処理や、代替処理などをここで行う
return 1; // エラー終了
} catch (const std::exception& e) {
// その他の標準例外を捕捉する場合
std::cerr << "Caught other exception: " << e.what() << std::endl;
return 1;
} catch (...) {
// 予期せぬ例外を捕捉する場合
std::cerr << "Caught unknown exception." << std::endl;
return 1;
}
// std::vectorによる例
try {
std::cout << "\nAttempting to create a huge std::vector..." << std::endl;
std::vector<int> bigVector;
bigVector.reserve(HUGE_SIZE); // 内部でnewを呼び出す可能性がある
std::cout << "Successfully reserved " << HUGE_SIZE << " elements for vector." << std::endl;
// 実際に要素を追加してメモリを消費させる
// for (size_t i = 0; i < HUGE_SIZE; ++i) {
// bigVector.push_back(0);
// }
} catch (const std::bad_alloc& e) {
std::cerr << "Caught std::bad_alloc for std::vector: " << e.what() << std::endl;
std::cerr << "std::vector allocation failed. Exiting." << std::endl;
return 1;
}
std::cout << "\nProgram finished successfully (or handled bad_alloc)." << std::endl;
return 0; // 正常終了
}
解説
std::vector::reserve()
も内部でメモリ確保を行うため、同様にstd::bad_alloc
を発生させる可能性があります。e.what()
は、例外に関する短い説明文字列を返します。try-catch
ブロックでこの例外を捕捉し、エラーメッセージを表示しています。- この要求が失敗すると、
std::bad_alloc
例外がスローされます。 new int[HUGE_SIZE]
の部分で、システムが確保できないほどの巨大なメモリを要求しています。
new (std::nothrow) を使用した例
new
演算子の例外をスローしないバージョンであるnew (std::nothrow)
を使用すると、メモリ確保に失敗した場合にnullptr
が返されます。これにより、例外処理を必要とせずにエラーをチェックできます。
#include <iostream> // std::cout, std::cerr
#include <new> // std::nothrow
int main() {
const size_t HUGE_SIZE = 10000000000ULL; // 100億個のint (約40GB)
std::cout << "Attempting to allocate a huge array using new (std::nothrow)..." << std::endl;
int* bigArray = new (std::nothrow) int[HUGE_SIZE];
if (bigArray == nullptr) {
std::cerr << "Memory allocation failed using new (std::nothrow)." << std::endl;
// エラーメッセージの表示や、代替処理などをここで行う
return 1; // エラー終了
} else {
std::cout << "Successfully allocated " << HUGE_SIZE << " integers." << std::endl;
delete[] bigArray;
std::cout << "Memory freed." << std::endl;
}
std::cout << "Program finished." << std::endl;
return 0; // 正常終了
}
解説
- 例外処理が不要になるため、特定の状況ではコードが簡潔になる可能性がありますが、エラーが「見落とされやすい」という側面もあります。
- その後の
if (bigArray == nullptr)
でエラーをチェックし、適切な処理を行うことができます。 new (std::nothrow) int[HUGE_SIZE]
と記述することで、メモリ確保が失敗しても例外はスローされず、bigArray
にはnullptr
が代入されます。
std::set_new_handler
関数を使用すると、new
演算子がメモリ確保に失敗した際に呼び出されるカスタム関数(ハンドラ)を設定できます。このハンドラは、メモリを解放しようと試みたり、エラーメッセージを表示してプログラムを終了させたりするのに役立ちます。
注意点
std::set_new_handler
で設定されたハンドラは、デフォルトのnew
演算子(例外をスローする方)が失敗した場合に呼び出されます。new (std::nothrow)
を使用した場合は呼び出されません。ハンドラが呼び出された後、ハンドラが例外をスローするか、std::abort()
などでプログラムを終了しない限り、new
演算子は再度メモリ確保を試みます。
#include <iostream> // std::cout, std::cerr
#include <new> // std::bad_alloc, std::set_new_handler, std::new_handler
#include <cstdlib> // std::abort
// カスタムのnewハンドラ関数
void myNewHandler() {
std::cerr << "Custom new handler called! Out of memory or unable to allocate." << std::endl;
// ここで緊急のメモリ解放処理を試みることもできるが、
// ほとんどの場合、回復は困難
// プログラムを終了させる
std::abort(); // std::bad_allocをスローしても良い
}
int main() {
// カスタムハンドラを設定
std::set_new_handler(myNewHandler);
const size_t HUGE_SIZE = 10000000000ULL; // 100億個のint (約40GB)
try {
std::cout << "Attempting to allocate a huge array (with custom new handler)..." << std::endl;
int* bigArray = new int[HUGE_SIZE];
std::cout << "Successfully allocated " << HUGE_SIZE << " integers." << std::endl;
delete[] bigArray;
std::cout << "Memory freed." << std::endl;
} catch (const std::bad_alloc& e) {
// myNewHandlerがstd::bad_allocをスローするように実装した場合、ここに到達する
std::cerr << "Caught std::bad_alloc after new handler: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cerr << "Caught other exception: " << e.what() << std::endl;
}
std::cout << "Program finished (if not aborted by handler)." << std::endl;
return 0;
}
- ハンドラが呼び出された後、プログラムの回復が難しい場合は、
std::abort()
で終了させるのが一般的です。 - この例では、ハンドラ内でエラーメッセージを出力し、
std::abort()
でプログラムを強制終了しています。 myNewHandler
関数は、new
がメモリ確保に失敗した場合に呼び出されます。
しかし、すべての状況で例外をスローしてプログラムの流れを中断するのが最適なアプローチとは限りません。以下に、std::bad_alloc
を直接処理する以外の代替手段や、メモリ不足に備えるためのプログラミング手法を説明します。
new (std::nothrow) を使用する
これは最も直接的な代替手段であり、すでに例で示しました。new
演算子の特殊な形式で、メモリ確保に失敗した場合に例外をスローする代わりにnullptr
を返します。
特徴
- 柔軟性
エラー時にプログラムを終了させるだけでなく、代替のリソースを使用したり、処理を中断したりするなど、より柔軟なエラーハンドリングが可能です。 - エラーチェック
戻り値がnullptr
かどうかをチェックすることで、メモリ確保の成功・失敗を判断します。 - 例外安全性
例外がスローされないため、try-catch
ブロックによる例外処理が不要になります。
使用例
#include <iostream>
#include <new> // std::nothrow
int main() {
size_t size = 10000000000ULL; // 非常に大きなサイズ
int* data = new (std::nothrow) int[size]; // 例外をスローしないnew
if (data == nullptr) {
std::cerr << "メモリ確保に失敗しました。代替処理を実行します。" << std::endl;
// エラーログの出力、リソースの解放、代替アルゴリズムへの切り替えなど
return 1; // エラー終了
}
std::cout << "メモリを正常に確保しました。" << std::endl;
// メモリを使用する処理
delete[] data;
return 0;
}
考慮事項
std::vector
などの標準コンテナは、通常new (std::nothrow)
を使用しないため、コンテナのメモリ確保失敗時にはstd::bad_alloc
がスローされます。- メモリ確保の失敗が頻繁に起こり得る状況(例:リソースが極めて限定的な組み込みシステム)で、プログラムの継続性が重要な場合に適しています。
コンテナの事前サイズ調整 (reserve / resize) とエラーハンドリング
std::vector
などのコンテナは、要素を追加する際に内部的にメモリを再確保します。この再確保時にstd::bad_alloc
が発生する可能性があります。reserve()
やresize()
を使用して事前にメモリを確保することで、メモリ確保のタイミングを制御し、そのタイミングでstd::bad_alloc
を捕捉できます。
特徴
- 効率性
不必要なメモリの再確保を減らし、パフォーマンスを向上させることができます。 - 予測可能性
メモリ確保の失敗を特定のポイントに集中させることができます。
使用例
#include <iostream>
#include <vector>
#include <new> // std::bad_alloc
int main() {
std::vector<int> myVector;
size_t desiredCapacity = 1000000000ULL; // 10億要素
try {
std::cout << "std::vectorの容量を予約しようとしています..." << std::endl;
myVector.reserve(desiredCapacity); // ここでメモリ確保が行われる可能性がある
std::cout << "容量の予約に成功しました。現在の容量: " << myVector.capacity() << std::endl;
// ここで実際に要素を追加していく
// for (size_t i = 0; i < desiredCapacity; ++i) {
// myVector.push_back(0); // push_backは通常、reserveしていれば再確保しない
// }
} catch (const std::bad_alloc& e) {
std::cerr << "std::vectorのメモリ確保に失敗しました: " << e.what() << std::endl;
std::cerr << "処理を中止します。" << std::endl;
return 1;
}
std::cout << "プログラムは正常に続行されました。" << std::endl;
return 0;
}
考慮事項
resize()
は要素のコンストラクトも伴うため、単にメモリ容量を確保する目的であればreserve()
が適切です。push_back
などで要素を追加する際に、reserve
した容量を超過すると再び再確保が発生し、そのタイミングでstd::bad_alloc
が送出される可能性があります。
オブジェクトプール / カスタムアロケータを使用する
アプリケーションが頻繁に同じサイズのオブジェクトを生成・破棄する場合、メモリの断片化やnew
/delete
のオーバーヘッドが問題になることがあります。このような場合に、あらかじめ大きなメモリブロックを確保しておき、そこからオブジェクトを割り当てる「オブジェクトプール」や、std::allocator
を継承した「カスタムアロケータ」を実装することが有効です。
特徴
- 予測可能な動作
メモリ確保の失敗を、プールの初期化時やプールの容量不足時など、特定のタイミングに限定できます。 - パフォーマンス向上
new
/delete
のシステムコールオーバーヘッドを削減します。 - 断片化の軽減
特定のサイズのオブジェクトに特化したメモリ管理を行うことで、メモリ断片化を抑制できます。
使用例 (概念)
// これは概念的なコードであり、完全なオブジェクトプールではありません。
// 実際の使用にはより堅牢な実装が必要です。
#include <iostream>
#include <vector>
#include <new> // std::bad_alloc
class MyObject {
int data[1024]; // 1KBのオブジェクト
public:
MyObject() { /* ... */ }
~MyObject() { /* ... */ }
};
class ObjectPool {
private:
std::vector<char> buffer;
std::vector<bool> usedFlags;
size_t objectSize;
size_t poolCapacity;
public:
ObjectPool(size_t capacity) : objectSize(sizeof(MyObject)), poolCapacity(capacity) {
try {
buffer.reserve(objectSize * poolCapacity); // 大量のメモリを一度に確保
usedFlags.resize(poolCapacity, false);
std::cout << "オブジェクトプールを初期化しました。" << std::endl;
} catch (const std::bad_alloc& e) {
std::cerr << "オブジェクトプールの初期化に失敗しました: " << e.what() << std::endl;
throw; // 初期化失敗を伝播
}
}
MyObject* allocate() {
for (size_t i = 0; i < poolCapacity; ++i) {
if (!usedFlags[i]) {
usedFlags[i] = true;
// バッファ内のアドレスをMyObject*にキャストして返す
return reinterpret_cast<MyObject*>(buffer.data() + i * objectSize);
}
}
std::cerr << "オブジェクトプールが枯渇しました。" << std::endl;
return nullptr; // プール枯渇
}
void deallocate(MyObject* obj) {
// オブジェクトがプール内の有効なアドレス範囲にあることを確認
// (ここでは省略)
size_t index = (reinterpret_cast<char*>(obj) - buffer.data()) / objectSize;
if (index < poolCapacity) {
usedFlags[index] = false;
}
}
};
int main() {
try {
ObjectPool pool(100000); // 10万個のMyObjectを格納できるプール
std::vector<MyObject*> allocatedObjects;
for (int i = 0; i < 100000; ++i) {
MyObject* obj = pool.allocate();
if (obj) {
// obj->someMethod(); // オブジェクトを使用
allocatedObjects.push_back(obj);
} else {
std::cerr << "プールの枯渇によりオブジェクトを確保できませんでした。" << std::endl;
break;
}
}
// オブジェクトの解放
for (MyObject* obj : allocatedObjects) {
pool.deallocate(obj);
}
} catch (const std::bad_alloc& e) {
std::cerr << "プール作成中にstd::bad_allocが発生しました: " << e.what() << std::endl;
return 1;
}
return 0;
}
考慮事項
- 通常、ゲーム開発やHPC (High-Performance Computing) など、非常に厳しい性能要件やメモリ管理の要件がある場合に検討されます。
- 異なるサイズのオブジェクトには対応しにくい場合があります(汎用的なアロケータはさらに複雑)。
- 実装は複雑になります。
仮想メモリのマッピング (mmap, MapViewOfFile)
非常に大きなデータセットを扱う場合、物理メモリに全てをロードするのではなく、ファイルシステムやOSの仮想メモリ機能を利用して、必要に応じてデータをページイン/アウトする方法があります。これは、std::bad_alloc
が発生するような巨大な連続メモリ確保を回避するのに役立ちます。
特徴
- ファイルI/Oの抽象化
ファイルの内容をメモリのように扱えるため、I/Oコードが簡素化されます。 - オンデマンドなメモリ使用
実際にアクセスされたデータのみが物理メモリにロードされます。 - 巨大なデータセットの処理
物理メモリの制限を超えたデータセットを扱うことができます。
主要なAPI
- Windows
CreateFileMapping()
,MapViewOfFile()
- Linux/macOS
mmap()
考慮事項
- メモリマッピングされた領域のポインタが無効になる可能性を考慮する必要があります。
- メモリマッピングされた領域へのアクセスは、ページフォルトやディスクI/Oを伴うため、パフォーマンス特性が通常のメモリとは異なります。
- プラットフォーム固有のAPIを使用するため、移植性が低下します。
最も根本的な解決策として、そもそも大量のメモリを必要としないように設計を見直したり、メモリ効率の良いアルゴリズムを選択したりすることが挙げられます。
特徴
- リソース効率
メモリだけでなく、CPUやディスクI/Oの効率も向上する可能性があります。 - 根本的解決
メモリ不足の問題そのものを回避します。
- ディスクベースのソリューション
大量のデータをディスク上に保存し、必要に応じて少しずつ読み込む(例:データベースの利用)。 - 並列処理と分散処理
データを複数のノードやスレッドに分散させ、それぞれのメモリ消費量を抑える。 - 圧縮
データをメモリにロードする前に圧縮し、使用時に展開するなど。 - アルゴリズムの最適化
データを全てメモリにロードするのではなく、ストリーム処理やオンデマンド処理に切り替える。 - データ構造の選択
例えば、密な行列にはstd::vector
を使用し、疎な行列にはstd::map
やカスタムの疎行列表現を使用するなど、データの性質に応じた適切なデータ構造を選択します。