C++のoptionalクラスを完全解説!7つの実践サンプルコードで完全網羅

C++のoptionalクラスを学ぶための詳細な解説のイメージC++
この記事は約14分で読めます。

 

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

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

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

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

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

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

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

はじめに

C++言語において、optionalクラスは重要な役割を担っています。

この記事を読むことで、読者の皆さんはC++のoptionalクラスの基本から応用までを深く理解し、実際のコーディングに役立てることができるようになります。

特に初心者から上級者まで、幅広い層のプログラマーにとって有益な情報を提供することを目指しています。

●C++のoptionalクラスとは

C++のoptionalクラスは、値が存在するかもしれないし、存在しないかもしれない状況を表現するために使用されます。

これは、従来のC++では表現が難しかった「値の有無」を明確に表すための強力なツールです。

optionalはC++17で導入され、プログラムの安全性と表現力の向上に大きく寄与しています。

○optionalクラスの基本

optionalクラスは、ある型Tの値を保持するか、または何も保持しない状態を保持できるラッパークラスです。

これにより、返り値が「無いかもしれない」関数や、初期化されていない可能性のある変数を安全に扱うことができます。

例えば、データベースからのクエリ結果が存在しない場合や、何らかの理由で計算が失敗した場合などに、optionalを使用することが考えられます。

○optionalクラスが必要な理由

optionalクラスがC++に導入された主な理由は、プログラムの安全性を高めることにあります。

従来、C++では値が存在しないことを表現するために、特別な値(例えばポインタの場合はnullptr)を使用していましたが、これは明示的ではなく、誤解を招きやすいものでした。

optionalを使用することで、値の有無が明確になり、プログラマが意図しない挙動を防ぐことができます。

また、optionalはプログラムの意図をより読みやすく、理解しやすい形で表現することを可能にします。

●optionalクラスの基本的な使い方

C++におけるoptionalクラスの基本的な使い方を理解するには、まずoptional型の変数の宣言と初期化の方法を把握することが重要です。

optional型の変数は、通常の型と同様に宣言できますが、値を持たない状態(nullopt)で初期化されることが特徴です。

値を持たせる場合は、代入演算子を使用して値を設定します。

次に、optional型の変数が値を持っているかどうかを確認する方法です。

これにはhas_value()関数またはvalue()関数を使用します。

has_value()関数は、値が存在する場合にtrueを返し、そうでない場合にはfalseを返します。

一方、value()関数は値を返しますが、値が存在しない場合には例外を投げます。

○サンプルコード1:optionalオブジェクトの作成と基本操作

ここでは、optional型の変数の作成と基本操作を表すサンプルコードを紹介します。

#include <optional>
#include <iostream>

int main() {
    std::optional<int> opt; // 初期化されていないoptional型の変数

    // 値を設定
    opt = 5;

    // 値が存在するかを確認
    if (opt.has_value()) {
        std::cout << "optの値: " << opt.value() << std::endl;
    } else {
        std::cout << "optは値を持っていません" << std::endl;
    }

    return 0;
}

このコードでは、std::optional<int>型の変数optを宣言し、その後で値5を設定しています。

has_value()関数を用いて値が存在するかを確認し、存在する場合はその値を出力しています。

○サンプルコード2:値の取得とエラー処理

optional型の変数から値を取得する際には、値が存在しない場合に対処する必要があります。

value()関数を使うと、値が存在しない場合に例外が投げられます。

そのため、値が存在するかどうかを事前に確認するか、value_or()関数を使用してデフォルト値を設定することが推奨されます。

下記のサンプルコードでは、値が存在しない場合のエラー処理を表しています。

#include <optional>
#include <iostream>

int main() {
    std::optional<int> opt; // 値を持たないoptional型の変数

    try {
        // 値を取得(値が存在しない場合、例外が発生する)
        std::cout << "optの値: " << opt.value() << std::endl;
    } catch (const std::bad_optional_access& e) {
        std::cout << "エラー: " << e.what() << std::endl;
    }

    // デフォルト値を使用して値を取得
    std::cout << "optの値(デフォルト値あり): " << opt.value_or(-1) << std::endl;

    return 0;
}

このコードでは、初めにoptが値を持たない状態であるため、value()関数を呼び出すと例外が発生します。

その後、value_or()関数を使ってデフォルト値-1を取得しています。

●optionalクラスの応用例

C++のoptionalクラスは、単に変数が値を持つかどうかを表すだけでなく、様々な応用が可能です。

関数の戻り値として利用したり、オプショナルな関数の引数として使ったりすることで、プログラムの柔軟性と安全性を高めることができます。

また、複雑なデータ型と組み合わせることで、さらに高度なプログラミングが可能になります。

○サンプルコード3:関数の戻り値としての使用

関数が常に有効な値を返さない場合、optionalを戻り値として使用することが有効です。

例えば、データベースからのクエリ結果が見つからない場合などに、nulloptを返すことで、戻り値の有無を明示的に表現できます。

#include <optional>
#include <iostream>
#include <string>

std::optional<std::string> findUserById(int id) {
    // ユーザーが見つかればその名前を返し、見つからなければnulloptを返す
    if (id == 1) {
        return "Taro";
    } else {
        return std::nullopt;
    }
}

int main() {
    auto user = findUserById(1);
    if (user) {
        std::cout << "ユーザー名: " << *user << std::endl;
    } else {
        std::cout << "ユーザーが見つかりませんでした" << std::endl;
    }

    return 0;
}

このコードでは、ユーザーIDに基づいてユーザー名を検索する関数findUserByIdを定義しています。

この関数は、ユーザーが見つかった場合にその名前を、見つからない場合にはnulloptを返します。

○サンプルコード4:オプショナルな引数としての使用

関数の引数が常に必要でない場合、optionalを用いることで、引数の有無を柔軟に扱うことができます。

下記の例では、オプショナルな引数を持つ関数を表しています。

#include <optional>
#include <iostream>
#include <string>

void greet(std::optional<std::string> name) {
    if (name) {
        std::cout << "こんにちは、" << *name << "さん!" << std::endl;
    } else {
        std::cout << "こんにちは、ゲストさん!" << std::endl;
    }
}

int main() {
    greet("Yamada");
    greet(std::nullopt); // 名前なし

    return 0;
}

このコードでは、greet関数がオプショナルなname引数を受け取り、名前が提供された場合とされない場合で異なる挨拶を表示します。

○サンプルコード5:複雑なデータ型との組み合わせ

optionalは複雑なデータ型と組み合わせても使用できます。

これにより、より柔軟なデータ構造を持つプログラムを作成できます。

下記の例では、optionalを使って複雑な構造体と組み合わせた例を表しています。

#include <optional>
#include <iostream>
#include <string>

struct UserInfo {
    std::string name;
    int age;
};

std::optional<UserInfo> getUserInfo(int id) {
    if (id == 1) {
        return UserInfo{"Taro", 30};
    } else {


        return std::nullopt;
    }
}

int main() {
    auto userInfo = getUserInfo(1);
    if (userInfo) {
        std::cout << "名前: " << userInfo->name << ", 年齢: " << userInfo->age << std::endl;
    } else {
        std::cout << "ユーザー情報が見つかりませんでした" << std::endl;
    }

    return 0;
}

このコードでは、ユーザー情報を含む構造体UserInfoを定義し、特定のIDに基づいてユーザー情報を取得する関数getUserInfoを実装しています。

戻り値としてoptional<UserInfo>を使用することで、ユーザー情報が存在しない場合にnulloptを返すことができます。

●注意点と対処法

C++のoptionalクラスを使用する際には、いくつかの注意点があります。

これらの点を理解し、適切に対処することで、より安全かつ効率的にoptionalクラスを活用することができます。

○optionalクラスの誤用とその避け方

optionalクラスの誤用の一つに、値の確認なしにvalue()を呼び出すことが挙げられます。

これは値がない場合に例外を引き起こす可能性があります。

この問題を避けるためには、値の取得前にhas_value()で値の存在を確認するか、value_or()を使用してデフォルト値を提供することが推奨されます。

また、optionalオブジェクトに対して頻繁に値を設定したり取り出したりすると、パフォーマンスに影響を及ぼす可能性があります。

optionalは使用する際に注意深く検討し、必要な場合のみ使用することが重要です。

○パフォーマンスに関する考慮事項

optionalクラスの使用は、特に大きなデータ構造や複雑なオブジェクトに対してはパフォーマンス上のコストを伴う可能性があります。

例えば、大きなデータ構造をoptionalでラップすると、そのコピーまたは移動のコストが発生する可能性があります。

パフォーマンスへの影響を最小限に抑えるためには、optionalオブジェクトを作成する際は、可能な限り軽量なデータ型を使用することが望ましいです。

また、optionalを返す関数では、可能であれば参照型を使用するか、移動セマンティクスを活用することでパフォーマンスの低下を防ぐことができます。

●optionalクラスのカスタマイズ方法

C++におけるoptionalクラスは、カスタマイズが可能であり、特定の用途に合わせて調整することができます。

これには、比較演算子の定義やユーザー定義型との統合などが含まれます。

これらのカスタマイズにより、optionalの使用範囲を拡大し、より柔軟なコードを記述することが可能になります。

○サンプルコード6:カスタム比較演算子の定義

optionalクラスにカスタム比較演算子を定義することで、異なるoptionalオブジェクト間での比較を簡単に行うことができます。

下記のサンプルコードは、optionalオブジェクト間のカスタム比較演算子を定義する方法を表しています。

#include <optional>
#include <iostream>

bool operator==(const std::optional<int>& a, const std::optional<int>& b) {
    // 両方とも値を持たない、または両方とも同じ値を持つ場合にtrueを返す
    return (!a.has_value() && !b.has_value()) || (a.has_value() && b.has_value() && a.value() == b.value());
}

int main() {
    std::optional<int> opt1 = 5;
    std::optional<int> opt2 = 5;
    std::optional<int> opt3;

    std::cout << "opt1 と opt2 は等しい: " << (opt1 == opt2) << std::endl;
    std::cout << "opt1 と opt3 は等しい: " << (opt1 == opt3) << std::endl;

    return 0;
}

このコードでは、operator==を定義して、二つのoptional<int>オブジェクトが等しいかどうかを判断しています。

○サンプルコード7:ユーザー定義型とoptionalの統合

optionalはユーザー定義型と組み合わせて使用することができ、これにより、より複雑なデータ構造に対応することが可能になります。

下記のサンプルコードは、ユーザー定義型とoptionalを統合する方法を表しています。

#include <optional>
#include <iostream>
#include <string>

struct Address {
    std::string city;
    std::string street;
};

std::optional<Address> getAddress(int userId) {
    if (userId == 1) {
        return Address{"Tokyo", "Shinjuku"};
    } else {
        return std::nullopt;
    }
}

int main() {
    auto address = getAddress(1);
    if (address) {
        std::cout << "市区町村: " << address->city << ", 通り: " << address->street << std::endl;
    } else {
        std::cout << "アドレスが見つかりませんでした" << std::endl;
    }

    return 0;
}

このコードでは、Addressという構造体とoptional<Address>を使用して、特定のユーザーIDに対する住所情報を取得しています。

ユーザーIDに対応する住所が存在しない場合、std::nulloptが返されます。

まとめ

この記事では、C++のoptionalクラスの基本的な使い方から応用例、注意点、カスタマイズ方法までを詳細に解説しました。

初心者から上級者まで、C++のoptionalクラスを深く理解し、効果的に活用できるような知識を紹介しました。

optionalクラスの適切な使用は、プログラムの安全性と可読性を高めるために不可欠です。

このガイドを通じて、読者の皆さんがC++プログラミングのスキルを向上させることを願っています。