ChatGPTで学ぶプログラミング-スマートポインタ編-その2

1. はじめに

 前回は、ChatGPTからスマートポインタの簡単な概要を教わった。スマートポインタとは、通常のポインタよりも高度な機能を持ったポインタであり、特に、メモリの確保・解放を自動的に行えるメリットがある。

 また、C++のスマートポインタとして、unique_ptr、shared_ptr、week_ptrの3つが存在することがわかった。今回は、その中からunique_ptrについて詳しくChatGPTに教えてもらう。なお、裏どりはしていないため、情報を鵜呑みにしないこと。

2. unique_ptrとは

 まず、unique_ptrについて整理する。 

質問:C++のunique_ptrとはなんですか?

回答:std::unique_ptrはC++11以降の標準ライブラリに含まれるスマートポインタの一つです。このスマートポインタは、所有権を持つポインタとして設計されており、一つのstd::unique_ptrが一つの動的に割り当てられたオブジェクトへの所有権を独占します。言い換えれば、同じ動的に割り当てられたオブジェクトに対する所有権を複数のstd::unique_ptrが持つことはありません。

std::unique_ptrの主な特性は以下の通りです:
所有権の独占: あるstd::unique_ptrが一つのリソース(通常はヒープメモリ上のオブジェクト)を所有している場合、その所有権はそのstd::unique_ptrに独占されます。
自動的なリソース解放: std::unique_ptrがスコープを抜けるか、明示的に破棄されると、その所有していたリソース(メモリ)は自動的に解放されます。これによりメモリリークを防ぐことができます。
所有権の移動: std::unique_ptrはコピーコンストラクタや代入演算子を持っていないので、所有権を別のstd::unique_ptrに渡すときはstd::moveを使用して所有権を移動します。
カスタムデリータ: std::unique_ptrは、デストラクタが呼ばれるときに実行されるカスタムな解放処理(デリータ)を指定することもできます。

 上記をまとめるとunique_ptrは以下の特徴がある。
・所有権がユニーク(唯一)である。
・所有権は移動可能である。
・メモリ解放を自動で行うため、メモリリークが防げる。

3. unique_ptrの作成例

 次は、unique_ptrの簡単な記述例を確認する。

質問:C++でunique_ptrの最も簡単な例を作って。

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

int main() {
    // std::unique_ptrでint型のメモリを動的に確保し、そのポインタを保持
    std::unique_ptr<int> ptr(new int(5));

    // ptrが指す整数の値を出力
    std::cout << "Value pointed to by ptr: " << *ptr << std::endl;

    return 0;
    // main関数が終わる際にptrのデストラクタが呼ばれ、メモリが自動で解放される
}

 上記は、unique_ptrの簡単な使用例である。ユニークポインタptrにint型の動的な値を確保し、コンソール上に表示しているまた、main関数が終わる際に、ptrのデストラクタが呼ばれることでメモリが自動で解放できる。

 意外にも*ptrで中身を見れるところは、生ポインタと同じらしい。

 unique_ptrを作成する場合は、「new int()」と作る方法もあるが、make_unique関数から作成することもできる。

質問:C++のユニークポインタをmake_uniqueで作って
std::make_uniqueはC++14以降で利用できる機能で、std::unique_ptrをより安全かつ効率的に生成するための関数テンプレートです。以下に、std::make_uniqueを使用してstd::unique_ptrを作成する簡単な例を示します。

// make_uniqueを使用して、動的にint型のメモリを確保し、初期値として42を設定
auto ptr = std::make_unique<int>(42);

この例ではstd::make_unique関数を使用して、整数値42を格納する動的なメモリを確保しています。std::make_uniquestd::unique_ptrを返すので、ptr変数は所有権を持つことになります。そして、*ptrでその値を参照しています。

このプログラムが終了すると、std::unique_ptrのデストラクタが呼び出され、自動的に動的に確保したメモリが解放されます。これにより、メモリリークを防ぐことができます。

 上記のようにmake_uniqueを使用すると、簡単にunique_ptrを作ることができるようだ。ただし、C++14以降でないと使用できない。

4. 所有権の移動方法を確認する

 unique_ptrは所有権を一つしか持つことはできない。そこで、所有権をどのように移動できるか確認する。

質問:C++のunique_ptrの所有権を移動する簡単な例を作って

#include <iostream>
#include <memory>

int main() {
    // unique_ptrを作成し、整数10を動的に確保
    std::unique_ptr<int> ptr1 = std::make_unique<int>(10);

    // ptr1の所有権をptr2に移動
    std::unique_ptr<int> ptr2 = std::move(ptr1);

    // この時点で、ptr1は空(nullptr)になっている
    if (ptr1 == nullptr) {
        std::cout << "ptr1 is nullptr" << std::endl;
    }

    // ptr2が所有権を持っているので、その値を出力
    if (ptr2 != nullptr) {
        std::cout << "ptr2 points to: " << *ptr2 << std::endl;
    }

    return 0;
}

 unique_ptrの所有権を移動するには、std::moveを使用することで実現できる。また、このプログラムが終了する際に、メモリは自動解放するようだ。

 次は、関数呼び出しの方法を確認する。

質問:unique_ptrで関数呼び出しをする簡単な例を作って
回答:
std::unique_ptrを関数の引数にする場合、関数がその所有権を取得すると明示的でなければならないことを理解することが重要です。通常、std::unique_ptrを関数に渡すときは、std::moveを使用して所有権を移動させます。

#include <iostream>
#include <memory>

void takeOwnership(std::unique_ptr<int> p) {
    if (p != nullptr) {
        std::cout << "takeOwnership: " << *p << std::endl;
    }
}

int main() {
    auto myUniquePtr = std::make_unique<int>(42);

    std::cout << "main: " << *myUniquePtr << std::endl;

    // 所有権を関数に移動
    takeOwnership(std::move(myUniquePtr));

    // myUniquePtr はもう所有権を持っていないので nullptr
    if (myUniquePtr == nullptr) {
        std::cout << "main: myUniquePtr is now nullptr." << std::endl;
    }

    return 0;
}

 unique_ptrは、所有権は1つしかない。よって、関数呼び出しの場合でもmoveを使用して所有権を移動する必要がある。これには少し驚いた。そこで、ChatGPTに確認する。

質問:関数呼び出しのたびにstd::moveするのは面倒ではないですか?
回答
std::moveを頻繁に使って所有権を移す必要がある場面では、設計に問題がある可能性が高いです。std::unique_ptrは、所有権が1つであることを強制するためのスマートポインタです。そのため、同じリソースの所有権を頻繁に複数の場所に移す必要がある場合は、std::unique_ptrが適切な選択ではないかもしれません。

 どうやら頻繁に所有権を移す場合には、そもそもunique_ptrは向いていないようだ。ただし、参照渡しする方法もあると提示してきた。

#include <memory>
#include <iostream>
void useData(const int& data){
        std::cout << data << std::endl;
}
int main(){
        auto myUniquePtr = std::make_unique<int>(42);
        useData(*myUniquePtr);

        if (myUniquePtr == nullptr){
                std::cout << "nullptr " << std::endl;
        }
        return 0;

}

 上記は、参照渡しでスマートポインタを渡す方法である。このようにするとデータだけを渡すことができる。

5. 終わりに

 今回は、スマートポインタの一つであるunique_ptrについてChatGPTから教わった。わかったことは以下である。

・unique_ptrを使用すると、プログラム終了時にメモリを自動開放する
・make_uniqueでメモリ確保、moveで所有権を移動する。
・関数呼び出しをする際も、moveで所有権を移動できる。

この記事が気に入ったらサポートをしてみませんか?