C++スマートポインタで安全にリソースを解放する方法

C++は、強力なメモリ管理機能を持つプログラミング言語ですが、その反面、適切にリソースを解放しないとメモリリークやクラッシュなどの問題が発生しやすいという課題もあります。そこで役立つのが「スマートポインタ」です。スマートポインタを使用することで、自動的にリソースを管理し、安全かつ効率的なプログラムを作成することが可能になります。本記事では、C++のスマートポインタの基本から応用までを詳しく解説し、安全にリソースを解放する方法について学びます。

目次

スマートポインタの基本

スマートポインタは、C++標準ライブラリに含まれるテンプレートクラスで、動的に確保されたメモリを自動的に管理します。通常のポインタと異なり、スマートポインタは所有権の概念を導入し、メモリ管理をより安全かつ簡単に行うことができます。C++11で導入されたスマートポインタには、主にstd::unique_ptrstd::shared_ptrstd::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に所有権が移動し、ptr1nullになります。

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;
}

この例では、ptr1ptr2が同じリソースを共有し、リファレンスカウントは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;
}

この例では、node1node2が互いに参照し合うことで、循環参照が発生し、リソースが解放されません。これを防ぐためには、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;
}

この例では、weakPtrsharedPtrを参照し、所有権を持たずにリソースを観察できます。

循環参照の防止

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;
}

この例では、node1node2shared_ptrで参照し、node2node1weak_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_ptrshared_ptrweak_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_ptr1sharedPtr2が同じ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を使用することで、MyClassNodeの間の循環参照を防ぎ、適切にリソースを管理しています。

これらの実装例を参考にして、スマートポインタを効果的に利用することで、安全で効率的なリソース管理が可能になります。

スマートポインタを使用したプロジェクト例

ここでは、スマートポインタを活用して、簡単なプロジェクトを構築する例を示します。このプロジェクトでは、スマートポインタを使用して、複雑なリソース管理を行うことなく、メモリの安全性と効率性を確保します。

プロジェクトの概要

このプロジェクトでは、図書館の蔵書管理システムを実装します。本の情報や利用者の情報を管理し、借り出しや返却の処理を行います。スマートポインタを使用して、メモリ管理を安全かつ効率的に行います。

クラス構成

プロジェクトには、以下のクラスを含みます:

  1. Book – 本の情報を管理するクラス
  2. User – 利用者の情報を管理するクラス
  3. 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_ptrweak_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_ptrshared_ptrweak_ptrのそれぞれの使い方や利点、注意点を理解し、実際のプロジェクトでの応用例や演習問題を通じて、スマートポインタの効果的な使用方法を学びました。

スマートポインタを適切に活用することで、C++プログラムの安全性と効率性が大幅に向上します。この記事を参考にして、自身のプログラムでもスマートポインタを積極的に取り入れてみてください。

コメント

コメントする

目次