読み込み中...

初心者から上級者まで学べるC++でのunique_ptrの使い方9選

C++でのunique_ptrの使い方を分かりやすく解説するイメージ C++
この記事は約21分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

C++プログラミングを学ぶ上で、メモリ管理は重要なスキルの一つです。

特に、スマートポインタと呼ばれる機能は、メモリリークを防ぎ、より安全で効率的なコードを書くために不可欠です。

この記事では、その中でも「unique_ptr」というスマートポインタの使い方を、初心者から上級者まで幅広くカバーして解説します。

unique_ptrの基本的な概念から、より高度な応用方法までを、実例と共に分かりやすく説明しますので、この記事を読むことで、C++におけるメモリ管理の理解を深めることができるでしょう。

●unique_ptrとは

「unique_ptr」とは、C++11以降で導入されたスマートポインタの一種です。

これは、特定のオブジェクトへの所有権を表し、そのオブジェクトへの唯一のポインタとなります。

unique_ptrがスコープ外に出ると、自動的にオブジェクトを破棄し、メモリを解放します。

これにより、開発者はメモリ解放のためのコードを書く手間を省くことができ、メモリリークのリスクを減らすことができます。

unique_ptrは、リソースの自動解放、例外安全性、メモリ管理の単純化といった点で、生のポインタよりも優れた選択と言えます。

○unique_ptrの基本概念

unique_ptrは、所有権モデルに基づいて動作します。

これは、あるunique_ptrがあるオブジェクトに対する所有権を持ち、他のどのunique_ptrも同じオブジェクトに対して所有権を持つことはできません。

所有権があるということは、そのunique_ptrがオブジェクトに対する完全な制御権を持ち、そのオブジェクトのライフサイクルを管理するということです。

所有権があるunique_ptrが破棄されると、そのオブジェクトも自動的に破棄されます。

○unique_ptrの利点とは

unique_ptrの最大の利点は、自動的なメモリ管理です。

プログラマが明示的にdeleteを呼び出す必要がなく、オブジェクトのライフサイクルに関する懸念が軽減されます。

また、unique_ptrは例外が発生した場合でも、適切にオブジェクトを破棄し、リソースリークを防ぐことができます。

これは、特に大規模なプロジェクトや複雑なプログラムにおいて、安全性とメンテナンスの容易さを提供します。

さらに、unique_ptrは、オブジェクトの所有権を明確にすることで、コードの可読性と保守性を高めます。

所有権の移動が必要な場合、unique_ptrは「move semantics」を用いて所有権の移動を効率的に行うことができます。

これらの特徴により、unique_ptrは現代的なC++プログラミングにおいて重要な役割を果たします。

●unique_ptrの基本的な使い方

C++のunique_ptrは、スマートポインタの中でも特に使い勝手が良く、メモリ管理を容易にします。

unique_ptrを使用する基本的な方法は、オブジェクトの動的割り当てとそれらの自動的な解放です。

この機能により、プログラマはメモリリークの心配をせずに、安全にリソースを管理できます。

unique_ptrは、標準ライブラリのヘッダファイル内で定義されており、使用する前にこのヘッダをインクルードする必要があります。

○サンプルコード1:unique_ptrの基本的な宣言

unique_ptrを使用する最も基本的な方法は、新しいオブジェクトを作成し、それをunique_ptrに割り当てることです。

下記のサンプルコードは、int型のオブジェクトを作成し、それをunique_ptrに割り当てる方法を表しています。

#include <memory> // unique_ptrを使うために必要

int main() {
    std::unique_ptr<int> myUniquePtr(new int(10)); // int型のオブジェクトを割り当て
    return 0;
}

このコードでは、std::unique_ptr<int>型の変数myUniquePtrを宣言し、新しく作成されたint型のオブジェクトに対する所有権を与えています。

このオブジェクトは、new int(10)により動的に割り当てられ、その初期値は10に設定されています。

この方法でunique_ptrを使用すると、myUniquePtrがスコープを抜ける時(この場合はmain関数の終わり)に、自動的に関連するオブジェクトが破棄され、割り当てられたメモリが解放されます。

○サンプルコード2:オブジェクトの割り当てと解放

unique_ptrは、オブジェクトのライフサイクルを管理するため、オブジェクトの割り当てと解放を自動で行います。

下記のサンプルコードでは、unique_ptrを使ってオブジェクトを割り当て、その後自動的に解放する様子を表しています。

#include <memory>
#include <iostream>

class MyClass {
public:
    MyClass() { std::cout << "MyClass created\n"; }
    ~MyClass() { std::cout << "MyClass destroyed\n"; }
};

int main() {
    {
        std::unique_ptr<MyClass> myClassUniquePtr(new MyClass());
        // ここでMyClassのインスタンスが作成され、メッセージが表示される
    } // myClassUniquePtrがスコープを抜けると、MyClassのインスタンスが破棄され、メッセージが表示される

    return 0;
}

このコードでは、MyClassというシンプルなクラスを定義し、そのコンストラクタとデストラクタでメッセージを出力するようにしています。

main関数内で、std::unique_ptr<MyClass>型の変数myClassUniquePtrを宣言し、新しいMyClassインスタンスを割り当てています。

このスコープを抜ける際に、myClassUniquePtrによって管理されているMyClassインスタンスが自動的に破棄され、メモリが解放されます。

この様子は、デストラクタによって出力されるメッセージで確認できます。

●unique_ptrの応用的な使い方

C++のunique_ptrは、基本的な使い方にとどまらず、様々な応用的な使い方が可能です。

特に、ファクトリ関数との組み合わせ、配列との使用、カスタムデリータの使用といった応用テクニックは、unique_ptrの機能をさらに拡張し、柔軟なプログラミングを可能にします。

○サンプルコード3:ファクトリ関数と組み合わせた使用法

ファクトリ関数とunique_ptrを組み合わせることで、オブジェクトの生成と管理をより効率的に行えます。

下記のサンプルコードは、ファクトリ関数を通じてunique_ptrを返す方法を表しています。

#include <memory>

class MyClass {
public:
    MyClass(int value) : value_(value) {}
    int getValue() const { return value_; }
private:
    int value_;
};

std::unique_ptr<MyClass> createMyClass(int value) {
    return std::unique_ptr<MyClass>(new MyClass(value));
}

int main() {
    auto myClassPtr = createMyClass(10);
    // ここでmyClassPtrを通じてMyClassのインスタンスにアクセス
    return 0;
}

このコードでは、createMyClassというファクトリ関数を定義しています。

この関数はMyClassの新しいインスタンスを作成し、そのポインタをstd::unique_ptr<MyClass>として返します。

この方法では、オブジェクトの生成と同時にunique_ptrによる管理を開始でき、メモリ管理が簡素化されます。

○サンプルコード4:配列との使用法

unique_ptrは配列とも連携できます。

下記のコードは、int型の配列をunique_ptrで管理する例を表しています。

#include <memory>

int main() {
    std::unique_ptr<int[]> myArray(new int[10]);
    for (int i = 0; i < 10; ++i) {
        myArray[i] = i;
    }
    // ここでmyArrayを通じて配列の操作
    return 0;
}

このサンプルでは、std::unique_ptr<int[]>を使用してint型の配列を管理しています。

配列の要素には通常の配列と同じようにアクセスでき、unique_ptrがスコープを抜けるときに配列が自動的に破棄されます。

○サンプルコード5:カスタムデリータの使用法

unique_ptrでは、デフォルトのデリータの代わりにカスタムデリータを使用することもできます。

これにより、特定のクリーンアップ処理をカスタマイズすることが可能になります。

下記のコードは、カスタムデリータを使用する方法を表しています。

#include <iostream>
#include <memory>

struct CustomDeleter {
    void operator()(int* ptr) {
        std::cout << "CustomDeleter: Deleting ptr\n";
        delete ptr;
    }
};

int main() {
    std::unique_ptr<int, CustomDeleter> myPtr(new int(10), CustomDeleter());
    // ここでmyPtrを使用
    return 0;
}

このサンプルコードでは、CustomDeleterという構造体を定義し、operator()をオーバーロードしています。

このデリータは、unique_ptrによって管理されているオブジェクトが破棄される際に呼び出されます。

この方法により、unique_ptrのデリータをカスタマイズし、特定のクリーンアップ処理を実装することができます。

●unique_ptrと例外処理

C++プログラミングにおいて、例外処理は重要な役割を果たします。

特に、メモリ管理を行う際に例外が発生すると、リソースのリークが起こる可能性があります。

しかし、unique_ptrを使用することで、例外が発生した場合でもリソースが適切に解放されることを保証することができます。

これにより、プログラムの安全性と信頼性が大きく向上します。

○サンプルコード6:例外安全なコードの作成

unique_ptrを使うことで、例外が発生した際にもオブジェクトが適切に破棄されるため、例外安全なコードを容易に書くことができます。

下記のサンプルコードは、例外が発生してもunique_ptrによってオブジェクトが安全に破棄される様子を表しています。

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "MyClass created\n"; }
    ~MyClass() { std::cout << "MyClass destroyed\n"; }

    void doSomething() {
        throw std::runtime_error("Error occurred");
    }
};

int main() {
    try {
        std::unique_ptr<MyClass> myClassPtr(new MyClass());
        myClassPtr->doSomething();
    } catch (const std::exception& e) {
        std::cout << "Exception caught: " << e.what() << '\n';
        // ここでmyClassPtrは自動的に破棄される
    }
    return 0;
}

このコードでは、MyClassのインスタンスを作成し、doSomethingメソッドを呼び出しています。

このメソッド内で例外が投げられると、catchブロックが実行され、例外メッセージが出力されます。

この際、unique_ptrmyClassPtrによって管理されているMyClassのインスタンスは自動的に破棄され、デストラクタが呼ばれます。

これにより、リソースリークのリスクが軽減されます。

○サンプルコード7:unique_ptrと例外処理の組み合わせ

unique_ptrは例外処理と組み合わせて、より安全なコードを書くために使用することができます。

下記のサンプルコードは、複数のリソースを管理しつつ例外処理を行う例を表しています。

#include <iostream>
#include <memory>
#include <vector>

class Resource {
public:
    Resource(int id) : id_(id) { std::cout << "Resource " << id_ << " created\n"; }
    ~Resource() { std::cout << "Resource " << id_ << " destroyed\n"; }
private:
    int id_;
};

int main() {
    try {
        std::unique_ptr<Resource> res1(new Resource(1));
        std::unique_ptr<Resource> res2(new Resource(2));
        // 何かの処理を行う
        throw std::runtime_error("Error occurred");
        // ここには到達しない
    } catch (const std::exception& e) {
        std::cout << "Exception caught: " << e.what() << '\n';
        // res1とres2は自動的に破棄される
    }
    return 0;
}

このコードでは、Resourceクラスの2つのインスタンスがunique_ptrによって管理されています。

例外が発生すると、catchブロックが実行され、両方のResourceインスタンスは自動的に破棄されます。

このように、unique_ptrを使用することで、複数のリソースを例外が発生した場合でも安全にクリーンアップすることが可能です。

●unique_ptrと例外処理

C++プログラミングにおいて、例外処理は重要な役割を果たします。

特に、メモリ管理を行う際に例外が発生すると、リソースのリークが起こる可能性があります。

しかし、unique_ptrを使用することで、例外が発生した場合でもリソースが適切に解放されることを保証することができます。

これにより、プログラムの安全性と信頼性が大きく向上します。

○サンプルコード6:例外安全なコードの作成

unique_ptrを使うことで、例外が発生した際にもオブジェクトが適切に破棄されるため、例外安全なコードを容易に書くことができます。

下記のサンプルコードは、例外が発生してもunique_ptrによってオブジェクトが安全に破棄される様子を表しています。

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "MyClass created\n"; }
    ~MyClass() { std::cout << "MyClass destroyed\n"; }

    void doSomething() {
        throw std::runtime_error("Error occurred");
    }
};

int main() {
    try {
        std::unique_ptr<MyClass> myClassPtr(new MyClass());
        myClassPtr->doSomething();
    } catch (const std::exception& e) {
        std::cout << "Exception caught: " << e.what() << '\n';
        // ここでmyClassPtrは自動的に破棄される
    }
    return 0;
}

このコードでは、MyClassのインスタンスを作成し、doSomethingメソッドを呼び出しています。

このメソッド内で例外が投げられると、catchブロックが実行され、例外メッセージが出力されます。

この際、unique_ptrmyClassPtrによって管理されているMyClassのインスタンスは自動的に破棄され、デストラクタが呼ばれます。

これにより、リソースリークのリスクが軽減されます。

○サンプルコード7:unique_ptrと例外処理の組み合わせ

unique_ptrは例外処理と組み合わせて、より安全なコードを書くために使用することができます。

下記のサンプルコードは、複数のリソースを管理しつつ例外処理を行う例を表しています。

#include <iostream>
#include <memory>
#include <vector>

class Resource {
public:
    Resource(int id) : id_(id) { std::cout << "Resource " << id_ << " created\n"; }
    ~Resource() { std::cout << "Resource " << id_ << " destroyed\n"; }
private:
    int id_;
};

int main() {
    try {
        std::unique_ptr<Resource> res1(new Resource(1));
        std::unique_ptr<Resource> res2(new Resource(2));
        // 何かの処理を行う
        throw std::runtime_error("Error occurred");
        // ここには到達しない
    } catch (const std::exception& e) {
        std::cout << "Exception caught: " << e.what() << '\n';
        // res1とres2は自動的に破棄される
    }
    return 0;
}

このコードでは、Resourceクラスの2つのインスタンスがunique_ptrによって管理されています。

例外が発生すると、catchブロックが実行され、両方のResourceインスタンスは自動的に破棄されます。

このように、unique_ptrを使用することで、複数のリソースを例外が発生した場合でも安全にクリーンアップすることが可能です。

●unique_ptrの高度な使い方

unique_ptrは、C++のスマートポインタの中でも特に柔軟性が高く、高度なプログラミングテクニックにも対応しています。

例えば、unique_ptrの所有権の移動や、関数への引数としての渡し方など、高度な使い方が可能です。

これらのテクニックをマスターすることで、より効率的で安全なプログラムを作成することができます。

○サンプルコード8:unique_ptrの移動と転送

unique_ptrは、所有権を持つことができる唯一のスマートポインタであり、その所有権を他のunique_ptrに移動させることができます。

下記のサンプルコードは、unique_ptrの所有権を移動する方法を表しています。

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "MyClass created\n"; }
    ~MyClass() { std::cout << "MyClass destroyed\n"; }
};

std::unique_ptr<MyClass> createMyClass() {
    return std::unique_ptr<MyClass>(new MyClass());
}

int main() {
    std::unique_ptr<MyClass> myClassPtr1 = createMyClass();
    std::unique_ptr<MyClass> myClassPtr2 = std::move(myClassPtr1);
    // myClassPtr1はnullになり、所有権はmyClassPtr2に移動する
    return 0;
}

このコードでは、createMyClass関数がMyClassのインスタンスをunique_ptrとして返し、その所有権をmyClassPtr1が受け取ります。

次に、std::moveを用いてmyClassPtr1の所有権をmyClassPtr2に移動させています。

この操作により、myClassPtr1はnullになり、所有権はmyClassPtr2に移ります。

○サンプルコード9:unique_ptrを関数に渡す方法

unique_ptrは、関数の引数として渡すことも可能です。

下記のサンプルコードは、unique_ptrを関数に渡す方法を表しています。

#include <iostream>
#include <memory>

void processMyClass(std::unique_ptr<MyClass> ptr) {
    // 何かの処理を行う
}

int main() {
    std::unique_ptr<MyClass> myClassPtr(new MyClass());
    processMyClass(std::move(myClassPtr));
    // myClassPtrはnullになり、所有権はprocessMyClassに移動する
    return 0;
}

このコードでは、processMyClass関数がunique_ptr<MyClass>型の引数を受け取ります。

main関数では、new MyClass()で作成されたMyClassのインスタンスをunique_ptrに格納し、std::moveを用いてprocessMyClass関数に所有権を移動させています。

この操作により、myClassPtrはnullになり、所有権はprocessMyClass関数内のptrに移ります。

●注意点と対処法

unique_ptrを使用する際には、いくつかの注意点があります。

これらを理解し、適切に対処することで、unique_ptrの機能を最大限に活用し、安全で効率的なプログラミングを実現できます。

○メモリリークを避けるためのヒント

unique_ptrは自動的にメモリを管理し、リソースリークを防ぐ役割を持っています。

かし、不適切な使用はメモリリークを引き起こす可能性があります。

メモリリークを避けるためには、下記の点に注意してください。

  1. unique_ptrの所有権を他のポインタに無闇に渡さないこと。所有権の移動はstd::moveを用いて明示的に行います。
  2. unique_ptrがスコープを抜ける際に、それが指すオブジェクトも自動的に解放されることを確認します。
  3. 配列を使用する場合は、unique_ptrの配列版を使用し、正しい方法でオブジェクトを割り当てます。

○unique_ptrの正しい使い方

unique_ptrを効果的に使用するためには、下記のような正しい使い方を心掛けることが重要です。

  1. unique_ptrを使用することで、明示的なdelete呼び出しを避け、自動的なメモリ管理を実現します。
  2. ファクトリ関数や関数の引数、戻り値としてunique_ptrを使用する際には、所有権の移動を適切に行います。
  3. カスタムデリータを必要に応じて使用し、特殊なクリーンアップ処理が必要な場合に適応させます。

これらの点に注意してunique_ptrを使用することで、C++における効率的で安全なメモリ管理を実現できます。

これにより、より堅牢でメンテナンスしやすいプログラムを作成することが可能になります。

まとめ

この記事では、C++におけるunique_ptrの使い方について、基本から応用まで幅広く解説しました。

unique_ptrを使いこなすことで、メモリ管理を効率的かつ安全に行うことができます。

特に、unique_ptrの自動メモリ解放機能、例外処理との連携、所有権の移動といった高度な特徴を理解し活用することが重要です。

これらの知識を身につけることで、C++プログラミングのスキルが一段と向上し、より堅牢でメンテナンスしやすいコードを書くことが可能になります。