HOWTO · C++

C++で非void関数の末尾まで到達する診断への対処法

C++ の非 void 関数が閉じ波かっこまで到達してしまう理由、すべての return 経路をカバーする方法、そしてフォールバック値やコンパイラ警告が役立つ場面を学びます。

このページの内容

C++では、戻り値の型が void ではない関数は、到達可能なすべての経路で値を返す必要があります。ただし main は例外で、末尾に到達すると暗黙に 0 を返します。それ以外の非void関数で、値を返さずに到達可能な経路が閉じ波括弧まで進むと、動作は未定義です。コードや警告設定によっては、コンパイラがその経路を診断することもあります。問題を解決するには、各分岐をたどり、関数の目的に合った値を返すようにします。

エラーの原因を理解する

非void関数は、宣言された型の値を返すことを約束します。「control reaches end of non-void function」と呼ばれることの多いコンパイラ診断は、その値を返さずに閉じ波括弧まで到達しうる経路を示しています。問題は、関数の末尾に文が一つ足りないだけとは限りません。それ以前の条件分岐によって、一部の入力で結果が返されないことがあります。たとえば int add(int a, int b) { if (a > b) return a + b; } は、a <= b の場合に値を返しません。main 以外の非void関数がこのように末尾まで進むと動作は未定義であり、特定のクラッシュや結果が必ず起きるわけではありません。

次の例を実行します。

#include <iostream>

int add(int a, int b) {
    return a + b;
}

int main() {
    std::cout << add(5, 3) << '\n';
}
8

この入力では、プログラムは合計を返します。上のインライン例は診断の原因となる経路を示しており、実行可能な例では、すべての入力に対して値を返すことで問題を解決しています。

すべての経路で値を返す

条件をたどり、それぞれの場合にどのような結果になるべきかを考えます。加算では、オペランドの大小関係にかかわらず合計を返す必要があります。別の処理に条件が必要なら、最後に return を置いて残りの経路を処理できます。どちらの分岐も同じ処理をするなら、条件分岐を取り除きます。

#include <iostream>

int add(int a, int b) {
    if (a > b) {
        return a + b;
    }
    return a + b;
}

int main() {
    std::cout << add(3, 5) << '\n';
}

場合によって意味が異なるときは、分岐をすべて明示すると便利です。次の例では両方の経路を処理し、大きい方のオペランドを返します。

#include <iostream>

int larger(int a, int b) {
    if (a > b) {
        return a;
    } else {
        return b;
    }
}

int main() {
    std::cout << larger(5, 3) << '\n';
}

入れ子になった条件や switch 文も確認してください。非void関数では、各経路で値を返すか、閉じ波括弧まで到達しないようにする必要があります。

既定の戻り値を使う

最後の return は、先行する条件に一致しない場合の代替結果として使えます。ただし、その値が関数の契約に合う場合に限ります。たとえば、名前に一致するスコアがあればそれを返し、名前が見つからなければ 0 を返す検索関数を考えてみましょう。名前が見つからない場合のスコアをアプリケーションがゼロと定義しているなら、この代替値は適切です。

#include <iostream>
#include <string>

int scoreFor(const std::string& name) {
    if (name == "Ada") {
        return 10;
    }
    return 0;
}

int main() {
    std::cout << scoreFor("Ada") << '\n';
    std::cout << scoreFor("Lin") << '\n';
}

該当する項目がないこと自体が別の結果を意味するなら、0 を返すとその違いが隠れてしまうことがあります。実際の契約を表せる戻り値の型と結果を選びましょう。たとえば、オプショナルな値や明示的なステータスを使えます。診断を消すためだけに、根拠のない代替値を追加しないでください。

コンパイラ警告を使ってデバッグする

コンパイル時に警告を有効にすると、確認すべき経路をコンパイラが見つける助けになります。たとえば g++ -Wall -Wextra -Wreturn-type your_code.cpp -o your_program は、該当する場合に戻り値の経路に関する診断を含む、さまざまな警告をGCCに報告させます。警告の有無や文言は、コンパイラ、バージョン、オプション、コードによって異なります。警告は制御フローをたどる手がかりであり、すべてのコンパイラが同じ扱いをする証拠ではありません。

入れ子になった if 文や switch の各ケースを含め、すべての分岐を確認します。そのうえで、各入力に対して関数がどう振る舞うべきかを決め、その動作に沿った値を返します。開発中は警告を有効にしておくと、同様の経路に気づきやすくなります。

まとめ

C++の非void関数が値を返さずに閉じ波括弧まで到達しうる場合は、その経路をたどって関数のロジックを修正します。それぞれの場合に、関数の目的に合った値を返してください。代替値を使うのは、その意味が明確な場合に限ります。コンパイラ警告は、確認すべき経路を見つける助けとして活用しましょう。

よくある質問

「control reaches end of non-void function」という診断の原因は何ですか?

値を返す関数で、到達可能な経路が値を返さずに閉じ波括弧まで進むことが原因です。コンパイラがこの状態を報告することがありますが、診断内容やビルド時の扱いはコンパイラとオプションによって異なります。

すべての if 分岐に return が必要ですか?

いいえ。条件分岐の後に return を置いて、残りの経路を処理できる場合があります。大切なのは、到達可能な経路が値を返さずに末尾まで進まないことです。

警告を消すために return 0; を追加すべきですか?

関数の契約上、ゼロが有効な結果である場合に限ります。そうでなければ、該当するケースを正確に表す結果や戻り値の型を選びましょう。

コンパイラ警告で値を返さない経路を特定できますか?

末尾まで進む可能性のある関数を警告で指摘できることがあります。その情報を使って制御フローを確認してください。警告の文言や動作は、コンパイラや設定によって異なります。