C++においてプログラムの実行フローを制御する「条件分岐」は、もっとも基礎的でありながら、もっとも重要な要素の一つです。

その中心を担うのがif文です。

特定の条件が満たされたときだけ処理を実行する、あるいは条件に応じて異なる処理を切り替えるというロジックは、どのようなアプリケーション開発においても欠かせません。

C++のif文は、単純な真偽判定から、C++17以降で導入された初期化文付きの形式、さらにはメタプログラミングで活用されるif constexprまで、多岐にわたる進化を遂げてきました。

本記事では、初心者の方が確実にマスターすべき基本構文から、中上級者が知っておくべきモダンなC++の応用的な書き方、さらには陥りやすいバグの回避策までを徹底的に解説します。

この記事を読むことで、C++における条件分岐を自在に操り、読みやすく効率的なコードを記述できるようになるでしょう。

C++におけるif文の基本構文

C++のif文は、指定した条件式が「真 (true)」である場合に、続くブロック内の処理を実行する制御構造です。

まずは、最もシンプルな記述方法から確認していきましょう。

基本的な記述方法

もっとも基本的なif文の形は以下の通りです。

C++
#include <iostream>

int main() {
    int score = 85;

    // scoreが80以上の場合にメッセージを表示する
    if (score >= 80) {
        std::cout << "合格です!" << std::endl;
    }

    return 0;
}
実行結果
合格です!

この例では、score >= 80という条件が評価されます。

この条件がtrue(真)であれば、波括弧 {} 内の処理が実行されます。

もし条件がfalse(偽)であれば、ブロック内の処理はスキップされ、プログラムは次の行へと進みます。

else句による分岐

条件が満たされなかった場合の処理を記述するには、elseキーワードを使用します。

これにより、「Aならば処理1を、そうでなければ処理2を」といった二者択一の処理を表現できます。

C++
#include <iostream>

int main() {
    int age = 15;

    if (age >= 18) {
        std::cout << "成人です。" << std::endl;
    } else {
        std::cout << "未成年です。" << std::endl;
    }

    return 0;
}
実行結果
未成年です。

else if句による多方向分岐

3つ以上の条件を順に判定したい場合は、else ifを組み合わせて使用します。

判定は上から順番に行われ、最初に条件が一致したブロックのみが実行されます。

C++
#include <iostream>

int main() {
    int temperature = 25;

    if (temperature >= 30) {
        std::cout << "今日は暑いです。" << std::endl;
    } else if (temperature >= 20) {
        std::cout << "過ごしやすい天気です。" << std::endl;
    } else {
        std::cout << "少し肌寒いです。" << std::endl;
    }

    return 0;
}
実行結果
過ごしやすい天気です。

ここで重要なのは、一度いずれかの条件が成立すると、それ以降のelse ifelseの判定は行われないという点です。

そのため、条件の記述順序には注意が必要です。

条件式で使用する演算子

if文の括弧内には、結果としてbool型(真または偽)を返す式を記述します。

ここで頻繁に使われるのが「比較演算子」と「論理演算子」です。

比較演算子

値を比較するために使用する演算子です。

演算子意味使用例
==等しいa == b
!=等しくないa != b
<より小さいa < b
>より大きいa > b
<=以下a <= b
>=以上a >= b

特に注意したいのが、等価比較の == です。

代入演算子である = と書き間違えると、意図しない挙動やコンパイルエラー(あるいは論理バグ)の原因になります。

論理演算子

複数の条件を組み合わせる際に使用します。

  • 論理積 (&&):「かつ(AND)」。全ての条件が真の場合に真となります。
  • 論理和 (||):「または(OR)」。いずれかの条件が真の場合に真となります。
  • 論理否定 (!):「〜ではない(NOT)」。真偽値を反転させます。
C++
#include <iostream>

int main() {
    int hour = 14;
    bool is_holiday = true;

    // 休日かつ、10時から18時の間であれば営業中とする
    if (is_holiday && (hour >= 10 && hour < 18)) {
        std::cout << "ただいま営業中です。" << std::endl;
    }

    return 0;
}
実行結果
ただいま営業中です。

C++には短絡評価 (Short-circuit evaluation)という仕組みがあります。

&& の左側が偽であれば、右側の式は評価されません。

同様に、|| の左側が真であれば、右側は評価されません。

これを利用して、ポインタのヌルチェックと値の参照を同時に行うといった記述がよく使われます。

初期化文付きif文 (C++17)

C++17以降では、if文の条件式の中で変数を宣言し、初期化することができるようになりました。

これを初期化文付きif文と呼びます。

構文とメリット

構文は以下の通りです。

if (初期化文; 条件式)

C++
#include <iostream>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> scores = {{"Alice", 90}, {"Bob", 75}};

    // scoresから"Alice"を検索し、見つかった場合のみその値を使う
    if (auto it = scores.find("Alice"); it != scores.end()) {
        std::cout << "Aliceのスコア: " << it->second << std::endl;
    } else {
        std::cout << "データが見つかりません。" << std::endl;
    }
    
    // 変数 it はこのスコープの外では使えないため、名前の衝突を防げる
    return 0;
}
実行結果
Aliceのスコア: 90

この書き方の最大の利点は、変数のスコープを最小限に抑えられることです。

検索用のイテレータや一時的なステータスコードなど、そのif文のブロック内でしか使わない変数を外部に漏らさないことで、コードの可読性と安全性が向上します。

コンパイル時条件分岐 (if constexpr)

C++17で導入されたもう一つの強力な機能が if constexpr です。

これはコンパイル時に条件を評価し、条件が満たされない方のブランチをコンパイル対象から除外します。

主にテンプレートプログラミング(ジェネリックプログラミング)で使用されます。

C++
#include <iostream>
#include <type_traits>

template <typename T>
void print_value(T v) {
    if constexpr (std::is_pointer_v<T>) {
        // Tがポインタ型の場合のみコンパイルされる
        std::cout << "ポインタの指す値: " << *v << std::endl;
    } else {
        // Tがポインタ型でない場合のみコンパイルされる
        std::cout << "値: " << v << std::endl;
    }
}

int main() {
    int val = 100;
    print_value(val);   // int型として呼び出し
    print_value(&val);  // int*型として呼び出し
    return 0;
}
実行結果
値: 100
ポインタの指す値: 100

通常のif文では、ポインタではない型を渡した場合でも、*v のような記述があるとコンパイルエラーになってしまいます。

しかし、if constexpr を使うことで、条件に合わないコードがコンパイル時に破棄されるため、型に応じた安全な処理の切り替えが可能になります。

C++23の新機能:if consteval

C++20で導入された std::is_constant_evaluated() の後継として、C++23では if consteval が追加されました。

これは、現在の処理が「定数評価(コンパイル時)」として実行されているかどうかを判定します。

C++
#include <iostream>

constexpr int compute(int n) {
    if consteval {
        // コンパイル時に実行される場合の最適化されたアルゴリズムなど
        return n * n;
    } else {
        // 実行時に呼び出される場合の処理
        return n + n;
    }
}

int main() {
    constexpr int res1 = compute(5); // 定数評価
    int x = 5;
    int res2 = compute(x);          // 実行時評価

    std::cout << "res1: " << res1 << std::endl;
    std::cout << "res2: " << res2 << std::endl;
    return 0;
}
実行結果
res1: 25
res2: 10

これにより、同じ関数内でコンパイル時と実行時で異なる実装を安全に使い分けることが容易になりました。

if文を記述する際のベストプラクティス

プログラムが複雑になると、if文が深くネスト(入れ子)になりがちです。

読みやすく保守しやすいコードを書くためのポイントを整理します。

ネストを浅く保つ(早期リターン)

条件を満たさない場合にすぐに処理を抜ける「早期リターン」を活用すると、コードの可読性が大幅に向上します。

悪い例(ネストが深い):

C++
void process(User* user) {
    if (user != nullptr) {
        if (user->is_active()) {
            if (user->has_permission()) {
                // 本来やりたい処理
            }
        }
    }
}

良い例(早期リターン):

C++
void process(User* user) {
    if (user == nullptr) return;
    if (!user->is_active()) return;
    if (!user->has_permission()) return;

    // 本来やりたい処理をインデントなしで記述できる
}

このように記述することで、メインのロジックがどこにあるのかが一目でわかるようになります。

複雑な条件式は関数や変数に切り出す

if文の括弧内が何行にも渡るような複雑な条件式になっている場合、その条件が「何を意味しているのか」を説明する変数名を付けるか、関数として抽出することを検討してください。

C++
// 修正前
if (age >= 18 && age <= 65 && !is_student && balance > 1000) { ... }

// 修正後
bool is_eligible_for_loan = (age >= 18 && age <= 65 && !is_student && balance > 1000);
if (is_eligible_for_loan) { ... }

波括弧 {} を省略しない

C++では、if文の後の処理が1行だけの場合、波括弧を省略できます。

しかし、これはバグの温床になりやすいため、原則として常に波括弧を記述することが推奨されます。

C++
// 省略した場合
if (condition)
    do_something();
    do_another(); // これはif文に関係なく常に実行されてしまう!

一見すると2つの関数が条件分岐に含まれているように見えますが、インデントに関わらず2つ目の関数は常に実行されます。

このようなミスを防ぐためにも、波括弧は省略すべきではありません。

if文と三項演算子の使い分け

単純な値の代入などには、条件演算子(三項演算子)を使う方が簡潔になる場合があります。

C++
// if文による代入
int max_val;
if (a > b) {
    max_val = a;
} else {
    max_val = b;
}

// 三項演算子による代入
int max_val = (a > b) ? a : b;

三項演算子は式であるため、値を直接返すことができます。

これにより、変数を const(定数)として初期化できるという大きなメリットがあります。

代入が必要な場面では、積極的に三項演算子や「即時実行ラムダ式」を活用しましょう。

よくある間違いとデバッグのポイント

C++のif文を扱う上で、特に初心者が注意すべきポイントをいくつか挙げます。

1. 代入演算子と等価演算子の取り違え

最も古典的なミスは、if (x = 5) のように書いてしまうことです。

これは変数 x に5を代入し、その結果(5 = true)を評価してしまいます。

これを防ぐために、最近のコンパイラは警告を出してくれますが、if (5 == x) のように定数を左側に書く「ヨーダ記法」というテクニックも存在します(ただし、現代では可読性の観点から好まれないことも多いです)。

2. 浮動小数点数の比較

doublefloat== で比較するのは危険です。

計算誤差により、厳密に一致しないことがあるためです。

C++
double d = 0.1 + 0.2;
if (d == 0.3) { // 偽になる可能性がある
    // ...
}

浮動小数点数の場合は、差の絶対値が非常に小さい閾値(エプシロン)以下であるかどうかで判定するのが一般的です。

3. 暗黙の型変換

C++ではポインタや整数が暗黙的にboolに変換されます。

ポインタであれば「ヌルポインタでない場合」、整数であれば「0でない場合」に真となります。

これが意図した挙動であれば良いですが、意味を明確にするために if (ptr != nullptr)if (count != 0) と明示的に書くことが好まれる場面も多いです。

まとめ

本記事では、C++におけるif文の基本から応用までを詳しく解説しました。

C++のif文は、単なる条件分岐の枠を超え、C++17の初期化文付きif文や if constexpr、さらにはC++23の if consteval といった強力な機能を備えています。

これらを適切に使い分けることで、変数のスコープを絞り込み、コンパイル時最適化の恩恵を受け、より安全で高性能なプログラムを記述することが可能になります。

最後に、重要なポイントを振り返りましょう。

  • 基本構文if, else if, else を適切に使い分ける。
  • 演算子:短絡評価の仕組みを理解し、安全な条件判定を行う。
  • モダンな記述:C++17以降の初期化文付きif文で変数のスコープを制御する。
  • メタプログラミングif constexpr でコンパイル時の型判定を効率化する。
  • クリーンコード:ネストを深くせず、早期リターンを心がける。

条件分岐をマスターすることは、C++の習得において非常に大きな一歩です。

この記事で紹介したテクニックを日々のコーディングに活かし、より洗練されたC++プログラムを目指してください。