C++は、強力なメモリ管理機能を持つプログラミング言語ですが、その反面、適切にリソースを解放しないとメモリリークやクラッシュなどの問題が発生しやすいという課題もあります。そこで役立つのが「スマートポインタ」です。スマートポインタを使用することで、自動的にリソースを管理し、安全かつ効率的なプログラムを作成することが可能になります。本記事では、C++のスマートポインタの基本から応用までを詳しく解説し、安全にリソースを解放する方法について学びます。
スマートポインタの基本
スマートポインタは、C++標準ライブラリに含まれるテンプレートクラスで、動的に確保されたメモリを自動的に管理します。通常のポインタと異なり、スマートポインタは所有権の概念を導入し、メモリ管理をより安全かつ簡単に行うことができます。C++11で導入されたスマートポインタには、主にstd::unique_ptr
、std::shared_ptr
、std::weak_ptr
の3種類があります。それぞれが異なるシナリオに適したメモリ管理を提供し、手動でメモリを解放する必要をなくすことで、メモリリークやダングリングポインタの問題を回避します。
unique_ptrの使い方
std::unique_ptr
は、単独の所有権を持つスマートポインタで、他のポインタに所有権を移すことはできません。これにより、特定のリソースが一つの所有者によってのみ管理されることを保証します。
unique_ptrの基本的な使用方法
std::unique_ptr
を使うには、以下のように動的メモリを確保します。
#include <memory>
#include <iostream>
int main() {
std::unique_ptr<int> ptr = std::make_unique<int>(10);
std::cout << *ptr << std::endl;
return 0;
}
std::make_unique
関数を使用することで、安全にstd::unique_ptr
を生成できます。
所有権の移動
std::unique_ptr
の所有権を別のポインタに移すには、std::move
関数を使用します。
#include <memory>
#include <iostream>
int main() {
std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
std::unique_ptr<int> ptr2 = std::move(ptr1);
if (ptr1) {
std::cout << "ptr1: " << *ptr1 << std::endl;
} else {
std::cout << "ptr1 is null" << std::endl;
}
std::cout << "ptr2: " << *ptr2 << std::endl;
return 0;
}
この例では、ptr1
からptr2
に所有権が移動し、ptr1
はnull
になります。
unique_ptrのカスタムデリータ
デフォルトのデリータではなく、カスタムデリータを使用する場合は、以下のように記述します。
#include <memory>
#include <iostream>
void customDeleter(int* p) {
std::cout << "Deleting pointer: " << *p << std::endl;
delete p;
}
int main() {
std::unique_ptr<int, decltype(&customDeleter)> ptr(new int(10), customDeleter);
return 0;
}
この例では、customDeleter
がポインタの削除に使用されます。
shared_ptrの使い方
std::shared_ptr
は、複数の所有者が同じリソースを共有できるスマートポインタです。リソースは最後の所有者が削除されたときに解放されます。これにより、リファレンスカウント方式でメモリ管理が行われます。
shared_ptrの基本的な使用方法
std::shared_ptr
を使うには、以下のように動的メモリを確保します。
#include <memory>
#include <iostream>
int main() {
std::shared_ptr<int> ptr1 = std::make_shared<int>(10);
std::shared_ptr<int> ptr2 = ptr1;
std::cout << "ptr1: " << *ptr1 << std::endl;
std::cout << "ptr2: " << *ptr2 << std::endl;
std::cout << "Reference count: " << ptr1.use_count() << std::endl;
return 0;
}
この例では、ptr1
とptr2
が同じリソースを共有し、リファレンスカウントは2となります。
所有権の共有
std::shared_ptr
は、複数のスマートポインタ間で所有権を共有できます。
#include <memory>
#include <iostream>
void display(std::shared_ptr<int> ptr) {
std::cout << "Value: " << *ptr << std::endl;
std::cout << "Reference count: " << ptr.use_count() << std::endl;
}
int main() {
std::shared_ptr<int> ptr1 = std::make_shared<int>(20);
display(ptr1);
std::shared_ptr<int> ptr2 = ptr1;
display(ptr2);
return 0;
}
この例では、display
関数内でも所有権が共有され、リファレンスカウントが更新されます。
shared_ptrの循環参照問題
std::shared_ptr
を使用する際の注意点として、循環参照によるメモリリークの問題があります。
#include <memory>
struct Node {
std::shared_ptr<Node> next;
};
int main() {
std::shared_ptr<Node> node1 = std::make_shared<Node>();
std::shared_ptr<Node> node2 = std::make_shared<Node>();
node1->next = node2;
node2->next = node1; // 循環参照
return 0;
}
この例では、node1
とnode2
が互いに参照し合うことで、循環参照が発生し、リソースが解放されません。これを防ぐためには、std::weak_ptr
を使用します。
weak_ptrの使い方
std::weak_ptr
は、std::shared_ptr
と共に使用される補助的なスマートポインタです。weak_ptr
は所有権を持たず、リファレンスカウントを増やさないため、循環参照を防ぐために使用されます。
weak_ptrの基本的な使用方法
std::weak_ptr
は、std::shared_ptr
から生成されます。
#include <memory>
#include <iostream>
int main() {
std::shared_ptr<int> sharedPtr = std::make_shared<int>(30);
std::weak_ptr<int> weakPtr = sharedPtr;
std::cout << "sharedPtr use count: " << sharedPtr.use_count() << std::endl;
std::cout << "weakPtr expired: " << std::boolalpha << weakPtr.expired() << std::endl;
if (auto lockedPtr = weakPtr.lock()) {
std::cout << "Locked value: " << *lockedPtr << std::endl;
} else {
std::cout << "Pointer is expired" << std::endl;
}
return 0;
}
この例では、weakPtr
がsharedPtr
を参照し、所有権を持たずにリソースを観察できます。
循環参照の防止
std::weak_ptr
は、循環参照を防ぐために使用されます。以下の例では、前述の循環参照の問題を解決します。
#include <memory>
#include <iostream>
struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 循環参照を防ぐためにweak_ptrを使用
};
int main() {
std::shared_ptr<Node> node1 = std::make_shared<Node>();
std::shared_ptr<Node> node2 = std::make_shared<Node>();
node1->next = node2;
node2->prev = node1;
std::cout << "node1 use count: " << node1.use_count() << std::endl;
std::cout << "node2 use count: " << node2.use_count() << std::endl;
return 0;
}
この例では、node1
がnode2
をshared_ptr
で参照し、node2
がnode1
をweak_ptr
で参照することで、循環参照を防ぎます。
weak_ptrの有効性チェック
std::weak_ptr
の有効性を確認するには、expired
メソッドやlock
メソッドを使用します。
#include <memory>
#include <iostream>
int main() {
std::shared_ptr<int> sharedPtr = std::make_shared<int>(40);
std::weak_ptr<int> weakPtr = sharedPtr;
sharedPtr.reset(); // sharedPtrをリセット
if (weakPtr.expired()) {
std::cout << "weakPtr is expired" << std::endl;
} else {
if (auto lockedPtr = weakPtr.lock()) {
std::cout << "Locked value: " << *lockedPtr << std::endl;
}
}
return 0;
}
この例では、sharedPtr
がリセットされた後でもweakPtr
の有効性を確認できます。
スマートポインタのメリット
スマートポインタを使用することで、C++のプログラムはより安全で効率的になります。以下に、スマートポインタの主要なメリットをいくつか挙げます。
メモリリークの防止
スマートポインタは、自動的にリソースを解放する機能を持つため、手動でメモリを解放し忘れることによるメモリリークを防ぎます。例えば、unique_ptr
は所有するリソースをスコープの終了時に自動的に解放します。
{
std::unique_ptr<int> ptr = std::make_unique<int>(50);
// ptrがスコープを離れるとき、メモリが自動的に解放される
}
安全なリソース管理
スマートポインタは所有権の概念を導入することで、リソースの二重解放やダングリングポインタの問題を防ぎます。shared_ptr
を使用することで、複数のポインタが同じリソースを共有し、最後の所有者が削除されたときにのみリソースが解放されます。
std::shared_ptr<int> ptr1 = std::make_shared<int>(60);
std::shared_ptr<int> ptr2 = ptr1; // ptr2も同じリソースを共有
例外安全性
スマートポインタは、例外が発生した場合でも自動的にリソースを解放するため、例外安全性を高めます。例えば、関数内で例外が発生した場合でも、スマートポインタは確実にリソースを解放します。
void func() {
std::unique_ptr<int> ptr = std::make_unique<int>(70);
throw std::runtime_error("Error occurred");
// 例外が発生しても、ptrはスコープを離れる際に自動的に解放される
}
コードの可読性と保守性の向上
スマートポインタを使用することで、明示的なリソース管理コードを減らし、コードの可読性と保守性を向上させることができます。スマートポインタの使用は、リソースの所有権とライフタイムを明確にし、他の開発者に対して意図を明示的に伝えます。
void process(std::unique_ptr<int> ptr) {
std::cout << "Processing value: " << *ptr << std::endl;
// ptrは関数の終了時に自動的に解放される
}
スレッドセーフなメモリ管理
shared_ptr
は、スレッドセーフなリファレンスカウントを持つため、マルチスレッド環境でも安全に使用できます。
#include <thread>
void threadFunc(std::shared_ptr<int> ptr) {
std::cout << "Thread value: " << *ptr << std::endl;
}
int main() {
std::shared_ptr<int> sharedPtr = std::make_shared<int>(80);
std::thread t1(threadFunc, sharedPtr);
std::thread t2(threadFunc, sharedPtr);
t1.join();
t2.join();
return 0;
}
この例では、shared_ptr
がマルチスレッド環境で安全に使用されています。
スマートポインタを適切に使用することで、C++プログラムの安全性、効率性、保守性が大幅に向上します。
スマートポインタのデメリット
スマートポインタは便利なツールですが、使用する際にはいくつかのデメリットや注意点があります。以下に、スマートポインタの主なデメリットを挙げます。
オーバーヘッドの増加
スマートポインタはリソース管理を自動化するため、通常のポインタに比べてオーバーヘッドが増加します。特にshared_ptr
はリファレンスカウントを管理するため、オーバーヘッドが大きくなることがあります。
#include <memory>
#include <iostream>
int main() {
std::shared_ptr<int> ptr1 = std::make_shared<int>(90);
std::shared_ptr<int> ptr2 = ptr1;
std::cout << "Reference count: " << ptr1.use_count() << std::endl;
return 0;
}
循環参照によるメモリリーク
shared_ptr
を使用する際に循環参照が発生すると、リソースが解放されずにメモリリークが発生します。これを防ぐためには、weak_ptr
を適切に使用する必要があります。
#include <memory>
struct Node {
std::shared_ptr<Node> next;
std::shared_ptr<Node> prev; // 循環参照によるメモリリーク
};
int main() {
std::shared_ptr<Node> node1 = std::make_shared<Node>();
std::shared_ptr<Node> node2 = std::make_shared<Node>();
node1->next = node2;
node2->prev = node1; // 循環参照発生
return 0;
}
所有権の曖昧さ
スマートポインタを適切に使用しないと、所有権が曖昧になり、コードの意図が不明確になることがあります。特に、shared_ptr
を多用すると、どの部分がリソースの所有者であるかが分かりにくくなることがあります。
#include <memory>
#include <iostream>
void process(std::shared_ptr<int> ptr) {
std::cout << "Processing value: " << *ptr << std::endl;
}
int main() {
std::shared_ptr<int> ptr = std::make_shared<int>(100);
process(ptr);
std::cout << "Reference count: " << ptr.use_count() << std::endl;
return 0;
}
学習コスト
スマートポインタを効果的に使用するには、所有権、ライフタイム管理、循環参照などの概念を理解する必要があります。これにより、初心者にとっては学習コストが高くなることがあります。
パフォーマンスの低下
スマートポインタは、特にshared_ptr
において、リファレンスカウントのインクリメントやデクリメントが頻繁に行われるため、パフォーマンスの低下を招くことがあります。リアルタイムシステムや高パフォーマンスが要求されるシステムでは注意が必要です。
#include <memory>
#include <vector>
int main() {
std::vector<std::shared_ptr<int>> vec;
for (int i = 0; i < 1000; ++i) {
vec.push_back(std::make_shared<int>(i));
}
return 0;
}
これらのデメリットを理解し、適切に管理することで、スマートポインタの効果を最大限に引き出すことができます。適材適所でスマートポインタを使用し、デメリットを最小限に抑えることが重要です。
スマートポインタの実装例
ここでは、C++のスマートポインタを使用してリソースを管理する具体的な例を示します。unique_ptr
、shared_ptr
、weak_ptr
のそれぞれについて、基本的な実装例を紹介します。
unique_ptrの実装例
std::unique_ptr
を使用して、動的メモリを管理する例です。
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor\n"; }
~MyClass() { std::cout << "MyClass destructor\n"; }
void display() const { std::cout << "Displaying MyClass instance\n"; }
};
int main() {
std::unique_ptr<MyClass> uniquePtr = std::make_unique<MyClass>();
uniquePtr->display();
// uniquePtrがスコープを抜けるときにMyClassのインスタンスが自動的に解放される
return 0;
}
この例では、unique_ptr
がスコープを抜けるときに、MyClass
のインスタンスが自動的に解放されます。
shared_ptrの実装例
std::shared_ptr
を使用して、複数の所有者が同じリソースを共有する例です。
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor\n"; }
~MyClass() { std::cout << "MyClass destructor\n"; }
void display() const { std::cout << "Displaying MyClass instance\n"; }
};
int main() {
std::shared_ptr<MyClass> sharedPtr1 = std::make_shared<MyClass>();
{
std::shared_ptr<MyClass> sharedPtr2 = sharedPtr1;
sharedPtr2->display();
std::cout << "Reference count: " << sharedPtr1.use_count() << std::endl;
}
// sharedPtr2がスコープを抜けると、参照カウントが減少するが、リソースは解放されない
std::cout << "Reference count after scope: " << sharedPtr1.use_count() << std::endl;
return 0;
}
この例では、shared_ptr1
とsharedPtr2
が同じMyClass
のインスタンスを共有し、sharedPtr2
がスコープを抜けても、MyClass
のインスタンスは解放されません。
weak_ptrの実装例
std::weak_ptr
を使用して、循環参照を防ぎながらリソースを管理する例です。
#include <iostream>
#include <memory>
class MyClass;
class Node {
public:
std::shared_ptr<MyClass> myClassPtr;
};
class MyClass {
public:
std::weak_ptr<Node> nodePtr; // weak_ptrを使用して循環参照を防ぐ
void display() const { std::cout << "Displaying MyClass instance\n"; }
};
int main() {
std::shared_ptr<MyClass> myClassPtr = std::make_shared<MyClass>();
std::shared_ptr<Node> nodePtr = std::make_shared<Node>();
myClassPtr->nodePtr = nodePtr;
nodePtr->myClassPtr = myClassPtr;
// weak_ptrを使用することで、循環参照を防ぎつつリソースを管理
if (auto lockedNodePtr = myClassPtr->nodePtr.lock()) {
std::cout << "Node is still alive\n";
} else {
std::cout << "Node has been destroyed\n";
}
return 0;
}
この例では、weak_ptr
を使用することで、MyClass
とNode
の間の循環参照を防ぎ、適切にリソースを管理しています。
これらの実装例を参考にして、スマートポインタを効果的に利用することで、安全で効率的なリソース管理が可能になります。
スマートポインタを使用したプロジェクト例
ここでは、スマートポインタを活用して、簡単なプロジェクトを構築する例を示します。このプロジェクトでは、スマートポインタを使用して、複雑なリソース管理を行うことなく、メモリの安全性と効率性を確保します。
プロジェクトの概要
このプロジェクトでは、図書館の蔵書管理システムを実装します。本の情報や利用者の情報を管理し、借り出しや返却の処理を行います。スマートポインタを使用して、メモリ管理を安全かつ効率的に行います。
クラス構成
プロジェクトには、以下のクラスを含みます:
Book
– 本の情報を管理するクラスUser
– 利用者の情報を管理するクラスLibrary
– 図書館全体の管理を行うクラス
クラス定義
#include <iostream>
#include <memory>
#include <vector>
#include <string>
class Book {
public:
Book(const std::string& title, const std::string& author)
: title(title), author(author) {
std::cout << "Book created: " << title << " by " << author << std::endl;
}
~Book() {
std::cout << "Book destroyed: " << title << " by " << author << std::endl;
}
void display() const {
std::cout << "Title: " << title << ", Author: " << author << std::endl;
}
private:
std::string title;
std::string author;
};
class User {
public:
User(const std::string& name) : name(name) {
std::cout << "User created: " << name << std::endl;
}
~User() {
std::cout << "User destroyed: " << name << std::endl;
}
void borrowBook(std::shared_ptr<Book> book) {
borrowedBooks.push_back(book);
std::cout << name << " borrowed " << book->getTitle() << std::endl;
}
void returnBook(std::shared_ptr<Book> book) {
auto it = std::find(borrowedBooks.begin(), borrowedBooks.end(), book);
if (it != borrowedBooks.end()) {
borrowedBooks.erase(it);
std::cout << name << " returned " << book->getTitle() << std::endl;
}
}
void displayBorrowedBooks() const {
std::cout << name << " has borrowed the following books:" << std::endl;
for (const auto& book : borrowedBooks) {
book->display();
}
}
private:
std::string name;
std::vector<std::shared_ptr<Book>> borrowedBooks;
};
class Library {
public:
void addBook(const std::string& title, const std::string& author) {
books.push_back(std::make_shared<Book>(title, author));
}
std::shared_ptr<Book> findBook(const std::string& title) {
for (auto& book : books) {
if (book->getTitle() == title) {
return book;
}
}
return nullptr;
}
private:
std::vector<std::shared_ptr<Book>> books;
};
メイン関数の実装
int main() {
Library library;
library.addBook("1984", "George Orwell");
library.addBook("To Kill a Mockingbird", "Harper Lee");
User user("Alice");
auto book1 = library.findBook("1984");
auto book2 = library.findBook("To Kill a Mockingbird");
if (book1) {
user.borrowBook(book1);
}
if (book2) {
user.borrowBook(book2);
}
user.displayBorrowedBooks();
user.returnBook(book1);
user.displayBorrowedBooks();
return 0;
}
説明
このプロジェクトでは、shared_ptr
を使用して、Book
オブジェクトの共有所有権を管理しています。Library
クラスは蔵書を管理し、User
クラスは本の借り出しと返却を管理します。shared_ptr
を使用することで、複数のUser
が同じBook
オブジェクトを安全に共有し、リソース管理の煩雑さを軽減しています。
このプロジェクトを通じて、スマートポインタの効果的な使用方法とその利点を実感できるでしょう。
スマートポインタの応用例
スマートポインタは、単にメモリ管理を自動化するだけでなく、複雑なデザインパターンや効率的なリソース管理のためにも使用されます。ここでは、スマートポインタの高度な使い方や応用例について紹介します。
ファクトリーパターン
ファクトリーパターンは、オブジェクトの生成を専門化するデザインパターンです。スマートポインタを使用することで、生成したオブジェクトの所有権を明確にし、メモリリークを防ぐことができます。
#include <iostream>
#include <memory>
class Product {
public:
Product() { std::cout << "Product created\n"; }
~Product() { std::cout << "Product destroyed\n"; }
void use() const { std::cout << "Using product\n"; }
};
class Factory {
public:
std::unique_ptr<Product> createProduct() {
return std::make_unique<Product>();
}
};
int main() {
Factory factory;
std::unique_ptr<Product> product = factory.createProduct();
product->use();
return 0;
}
この例では、Factory
クラスがProduct
オブジェクトを生成し、unique_ptr
で管理します。
カスタムデリータを使ったリソース管理
スマートポインタのカスタムデリータを利用することで、特定のリソース管理が可能になります。例えば、ファイルハンドルやネットワークリソースのクリーンアップに役立ちます。
#include <iostream>
#include <memory>
#include <cstdio>
struct FileDeleter {
void operator()(FILE* fp) const {
if (fp) {
std::cout << "Closing file\n";
fclose(fp);
}
}
};
int main() {
std::unique_ptr<FILE, FileDeleter> filePtr(fopen("example.txt", "w"));
if (filePtr) {
std::cout << "File opened\n";
}
// filePtrがスコープを抜けるときに、自動的にファイルが閉じられる
return 0;
}
この例では、FileDeleter
がファイルのクローズ処理を行い、unique_ptr
でファイルポインタを管理します。
ポリモーフィズムとスマートポインタ
スマートポインタは、継承とポリモーフィズムを利用する際にも効果的です。基底クラスのポインタをスマートポインタで管理することで、メモリ管理の煩雑さを軽減します。
#include <iostream>
#include <memory>
class Base {
public:
virtual ~Base() { std::cout << "Base destroyed\n"; }
virtual void display() const = 0;
};
class Derived : public Base {
public:
~Derived() { std::cout << "Derived destroyed\n"; }
void display() const override {
std::cout << "Displaying Derived class\n";
}
};
void process(std::unique_ptr<Base> ptr) {
ptr->display();
}
int main() {
std::unique_ptr<Base> basePtr = std::make_unique<Derived>();
process(std::move(basePtr));
// process関数内でbasePtrが自動的に解放される
return 0;
}
この例では、Derived
クラスがBase
クラスを継承し、unique_ptr
を使って基底クラスのポインタを管理します。
マルチスレッド環境でのリソース共有
shared_ptr
を使って、マルチスレッド環境で安全にリソースを共有することができます。
#include <iostream>
#include <memory>
#include <thread>
#include <vector>
class Resource {
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource released\n"; }
void use() const { std::cout << "Using resource\n"; }
};
void worker(std::shared_ptr<Resource> res) {
res->use();
}
int main() {
std::shared_ptr<Resource> resource = std::make_shared<Resource>();
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(worker, resource);
}
for (auto& t : threads) {
t.join();
}
return 0;
}
この例では、shared_ptr
を使って複数のスレッドが同じリソースを共有し、安全にアクセスしています。
これらの応用例を通じて、スマートポインタの柔軟性と利便性を理解し、より高度なC++プログラムの作成に役立てることができます。
スマートポインタの演習問題
スマートポインタの理解を深めるために、以下の演習問題を解いてみましょう。各問題の回答コードも提供しますので、自分で考えた後に確認してください。
問題1: unique_ptrの基本操作
unique_ptr
を使用して、動的にメモリを確保し、整数値を設定して表示するプログラムを作成してください。
回答例
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> ptr = std::make_unique<int>(10);
std::cout << "Value: " << *ptr << std::endl;
return 0;
}
問題2: shared_ptrの参照カウント
shared_ptr
を使用して、参照カウントの変化を確認するプログラムを作成してください。2つのshared_ptr
が同じリソースを共有する場面を実装してください。
回答例
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> ptr1 = std::make_shared<int>(20);
std::cout << "Reference count (ptr1): " << ptr1.use_count() << std::endl;
std::shared_ptr<int> ptr2 = ptr1;
std::cout << "Reference count (ptr1): " << ptr1.use_count() << std::endl;
std::cout << "Reference count (ptr2): " << ptr2.use_count() << std::endl;
ptr2.reset();
std::cout << "Reference count (ptr1): " << ptr1.use_count() << std::endl;
return 0;
}
問題3: weak_ptrによる循環参照の防止
shared_ptr
とweak_ptr
を使用して、循環参照を防ぐプログラムを作成してください。Node
クラスを使って、双方向リストを実装します。
回答例
#include <iostream>
#include <memory>
class Node {
public:
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev;
Node() { std::cout << "Node created\n"; }
~Node() { std::cout << "Node destroyed\n"; }
};
int main() {
std::shared_ptr<Node> node1 = std::make_shared<Node>();
std::shared_ptr<Node> node2 = std::make_shared<Node>();
node1->next = node2;
node2->prev = node1;
return 0;
}
問題4: カスタムデリータ
unique_ptr
を使用して、カスタムデリータを実装するプログラムを作成してください。ファイルを開いて書き込みを行い、unique_ptr
のカスタムデリータでファイルを閉じる処理を追加してください。
回答例
#include <iostream>
#include <memory>
#include <cstdio>
struct FileDeleter {
void operator()(FILE* fp) const {
if (fp) {
std::cout << "Closing file\n";
fclose(fp);
}
}
};
int main() {
std::unique_ptr<FILE, FileDeleter> filePtr(fopen("example.txt", "w"));
if (filePtr) {
std::cout << "File opened\n";
fprintf(filePtr.get(), "Hello, World!\n");
}
// filePtrがスコープを抜けるときに、自動的にファイルが閉じられる
return 0;
}
問題5: ポリモーフィズムとスマートポインタ
ポリモーフィズムを利用して、基底クラスのポインタをスマートポインタで管理するプログラムを作成してください。基底クラスを継承する2つの派生クラスを実装し、それぞれのクラスのメソッドを呼び出してください。
回答例
#include <iostream>
#include <memory>
class Base {
public:
virtual ~Base() { std::cout << "Base destroyed\n"; }
virtual void display() const = 0;
};
class Derived1 : public Base {
public:
~Derived1() { std::cout << "Derived1 destroyed\n"; }
void display() const override {
std::cout << "Displaying Derived1 class\n";
}
};
class Derived2 : public Base {
public:
~Derived2() { std::cout << "Derived2 destroyed\n"; }
void display() const override {
std::cout << "Displaying Derived2 class\n";
}
};
void process(std::unique_ptr<Base> ptr) {
ptr->display();
}
int main() {
std::unique_ptr<Base> basePtr1 = std::make_unique<Derived1>();
process(std::move(basePtr1));
std::unique_ptr<Base> basePtr2 = std::make_unique<Derived2>();
process(std::move(basePtr2));
return 0;
}
これらの演習問題を通じて、スマートポインタの実践的な使い方を理解し、メモリ管理のスキルを向上させましょう。
まとめ
本記事では、C++のスマートポインタを使った安全なリソース解放方法について詳しく解説しました。スマートポインタを使用することで、メモリリークやダングリングポインタの問題を防ぎ、例外安全性を確保しつつ、コードの可読性と保守性を向上させることができます。unique_ptr
、shared_ptr
、weak_ptr
のそれぞれの使い方や利点、注意点を理解し、実際のプロジェクトでの応用例や演習問題を通じて、スマートポインタの効果的な使用方法を学びました。
スマートポインタを適切に活用することで、C++プログラムの安全性と効率性が大幅に向上します。この記事を参考にして、自身のプログラムでもスマートポインタを積極的に取り入れてみてください。
コメント