C++ 開発において、動的確保されたメモリの解放忘れは重大なバグとなり得ます。従来の生ポインタでは明示的な delete 操作が必要ですが、std::shared_ptr を利用することで参照カウントベースの自動管理が可能になります。
ここでは、試験紙のライフサイクル(作成、回答、採点、破棄)を例に、生ポインタとスマートポインタの実装差異を比較します。
生ポインタによる実装
まず、従来の手法ではメモリの確保と解放をプログラマが責任を持って行う必要があります。以下のコードでは、 ExamSheet クラスのインスタンスを new で生成し、使用後に delete で明示的に解放しています。
#include <iostream>
#include <memory>
class ExamSheet {
public:
ExamSheet() {
std::cout << "ExamSheet initialized" << std::endl;
}
~ExamSheet() {
std::cout << "ExamSheet discarded" << std::endl;
}
static ExamSheet* allocate() {
return new ExamSheet();
}
};
void answerQuestions(ExamSheet*) {
std::cout << "Student answering questions" << std::endl;
}
void gradeAnswers(ExamSheet*) {
std::cout << "Teacher grading answers" << std::endl;
}
ExamSheet* runExam() {
ExamSheet* sheet = ExamSheet::allocate();
answerQuestions(sheet);
gradeAnswers(sheet);
std::cout << "Exam session finished" << std::endl;
return sheet;
}
int main() {
for (int i = 0; i < 2; ++i) {
ExamSheet* ptr = runExam();
delete ptr; // 明示的な解放が必要
}
std::cout << "All exams completed" << std::endl;
return 0;
}
この実装では、main 関数内のループで runExam から返されたポインタに対し、必ず delete を呼び出す必要があります。これを忘れると、試験が終わってもメモリが解放されず、リークが発生します。
std::shared_ptr による実装
次に、スマートポインタを用いて同じ処理を実装します。std::make_shared を使用してインスタンスを生成し、所有権を共有します。
#include <iostream>
#include <memory>
class ExamSheet {
public:
ExamSheet() {
std::cout << "ExamSheet initialized" << std::endl;
}
~ExamSheet() {
std::cout << "ExamSheet discarded" << std::endl;
}
static std::shared_ptr<ExamSheet> allocate() {
return std::make_shared<ExamSheet>();
}
};
void answerQuestions(std::shared_ptr<ExamSheet>) {
std::cout << "Student answering questions" << std::endl;
}
void gradeAnswers(std::shared_ptr<ExamSheet>) {
std::cout << "Teacher grading answers" << std::endl;
}
std::shared_ptr<ExamSheet> runExam() {
std::shared_ptr<ExamSheet> sheet = ExamSheet::allocate();
answerQuestions(sheet);
gradeAnswers(sheet);
std::cout << "Exam session finished" << std::endl;
return sheet;
}
int main() {
for (int i = 0; i < 2; ++i) {
runExam(); // 参照カウントが 0 になれば自動解放
}
std::cout << "All exams completed" << std::endl;
return 0;
}
関数のスコープを外れて参照カウントが 0 になった時点で、自動的にデストラクタが呼ばれます。main 関数内で delete を記述する必要はなく、戻り値を受け取らない場合でも適切にリソースが回収されます。実行時の出力メッセージは生ポインタ版と同様ですが、メモリ管理の負担が大幅に軽減されています。