関数 (C++)

関数とは、何らかの操作を実行するコードのブロックです。 関数には、呼び出し元が関数に引数を渡すのに使用する入力パラメーターを必要に応じて定義できます。 関数は、必要に応じて値を出力として返すことができます。 関数は、一般的な操作を 1 つの再利用可能なブロックでカプセル化するのに役立ちます。関数には、その動作を明確に説明する名前を付けることが理想的です。 次の関数は呼び出し元から 2 つの整数を受け取り、その合計を返します。abint 型のパラメーターです。

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

関数は、プログラム内の任意の数の場所から起動する、もしくは呼び出すことができます。 関数に渡される値は引数と呼ばれ、関数定義のパラメーターの型と互換性のある型を持つ必要があります。

int main()
{
    int i = sum(10, 32);
    int j = sum(i, 66);
    cout << "The value of j is" << j << endl; // 108
}

関数の長さに実際的な制限はありませんが、適切な設計は、1 つの明確に定義されたタスクを実行する関数を目的としています。 複雑なアルゴリズムは、可能な場合は常に、理解しやすい簡潔な関数に分割することをお勧めします。

クラス スコープで定義されている関数はメンバー関数と呼ばれます。 C++ では、他の言語とは異なり、名前空間スコープ (暗黙的なグローバル名前空間を含む) でも関数を定義できます。 このような関数は、フリー関数または非メンバー関数と呼ばれ、標準ライブラリで広く使用されています。

関数はオーバーロードされる場合があります。これは、異なるバージョンの関数が、仮パラメーターの数や型によって異なる場合に、同じ名前を共有する可能性があることを意味します。 詳細については、「関数オーバーロード」を参照してください。

関数宣言部分

最小関数宣言は、戻り値の型、関数名、およびパラメーター リスト (空の場合があります) と、コンパイラにより多くの命令を提供する省略可能なキーワード (keyword)で構成されます。 関数宣言の例を次に示します。

int sum(int a, int b);

関数定義は、宣言と本体で構成されます。本体とは、中かっこで挟まれたコード全体です。

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

関数宣言の後にはセミコロンが続きます。1 つのプログラムの複数個所で、関数宣言がなされる場合があります。 関数宣言は、翻訳単位ごとに関数に対する呼び出しの前に記述される必要があります。 関数定義は、単一定義規則 (ODR) に従い、プログラム内で 1 回だけ記述する必要があります。

関数宣言に必要な部分は次のとおりです。

  1. 戻り値の型とは関数が返す値の型を指定するもので、値が返されない場合は void です。 C++11 では、auto が有効な戻り値の型となっており、この型を指定するとコンパイラは return ステートメントから型を推論します。 C++14 では、decltype(auto) も使用できます。 詳細については、以下の「戻り値の型の型推論」を参照してください。

  2. 関数名。文字またはアンダースコアで始まる必要があり、スペースを含めることはできません。 一般に、標準ライブラリ関数名の先頭のアンダースコアは、プライベート メンバー関数、またはコードで使用することを意図していない非メンバー関数を示します。

  3. パラメーター リストとは、中かっこに挟まれ、コンマで区切られた 0 個以上のパラメーターのセットです。パラメーターで、型を指定し、関数本体内で値にアクセスする際に使用するローカル名も必要に応じて指定できます。

関数宣言の省略可能な部分は次のとおりです。

  1. constexpr。関数の戻り値がコンパイル時に計算できる定数値であることを示します。

    constexpr float exp(float x, int n)
    {
        return n == 0 ? 1 :
            n % 2 == 0 ? exp(x * x, n / 2) :
            exp(x * x, (n - 1) / 2) * x;
    };
    
  2. linkage specification、extern または static

    //Declare printf with C linkage.
    extern "C" int printf( const char *fmt, ... );
    
    

    詳細については、「翻訳単位とリンケージ」をご覧ください。

  3. inline: 関数のすべての呼び出しを関数コード自体に置き換えるようコンパイラに指示します。 パフォーマンスが重要なコード セクションで関数が迅速に実行され、繰り返し呼び出されるシナリオでは、インライン展開を使用すると、パフォーマンスが向上します。

    inline double Account::GetBalance()
    {
        return balance;
    }
    

    詳細については、「インライン関数」をご覧ください。

  4. noexcept 式。関数が例外をスローできるかどうかを指定します。 次の例では、式が > に評価された場合、関数は例外をis_podtrueスローしません。

    #include <type_traits>
    
    template <typename T>
    T copy_object(T& obj) noexcept(std::is_pod<T>) {...}
    

    詳細については、noexceptを参照してください。

  5. (メンバー関数のみ) cv 修飾子。関数が constvolatile を指定します。

  6. (メンバー関数のみ) virtualoverride、または finalvirtual は、派生クラスで関数をオーバーライドできることを指定します。 override は、派生クラス内の関数が仮想関数をオーバーライドすることを意味します。 final は、それ以降の派生クラスで関数をオーバーライドできないことを意味します。 詳細については、「仮想関数」を参照してください。

  7. (メンバー関数のみ) static メンバー関数に適用されるということは、関数がクラスのオブジェクト インスタンスに関連付けられていないことを意味します。

  8. (非静的メンバー関数のみ) ref 修飾子。暗黙的オブジェクト パラメーター (*this) が右辺値参照と左辺値参照の対になっている場合、関数のどのオーバーロードを選択すべきかをコンパイラに指定します。 詳細については、「関数オーバーロード」を参照してください。

関数の定義

関数定義は、宣言と関数本体で構成され、中かっこで囲まれています。これには、変数宣言、ステートメント、および式が含まれます。 次の例は、関数の完全な定義を示しています。

    int foo(int i, std::string s)
    {
       int value {i};
       MyClass mc;
       if(strcmp(s, "default") != 0)
       {
            value = mc.do_something(i);
       }
       return value;
    }

本体内で宣言された変数は、ローカル変数またはローカルと呼ばれます。 これらは関数の終了時にスコープ外に出るため、関数がローカルに参照を返すことはありません。

    MyClass& boom(int i, std::string s)
    {
       int value {i};
       MyClass mc;
       mc.Initialize(i,s);
       return mc;
    }

const 関数と constexpr 関数

メンバー関数 const を宣言して、クラス内のデータ メンバーの値の変更を関数が許可されないように指定できます。 メンバー関数を const として宣言すると、コンパイラは const-correctness を強制的に適用できます。 const として宣言された関数を使用してオブジェクトを誤って変更しようとすると、コンパイラ エラーが発生します。 詳細については、「const」をご覧ください。

生成された値がコンパイル時に決まる可能性がある場合、関数を constexpr として宣言します。 通常、constexpr 関数は通常の関数よりも高速に実行されます。 詳細については、constexprを参照してください。

関数テンプレート

関数テンプレートはクラス テンプレートに似ており、テンプレート引数に基づく具体的な関数を生成します。 多くの場合、テンプレートは型引数を推測できるので、型引数を明示的に指定する必要はありません。

template<typename Lhs, typename Rhs>
auto Add2(const Lhs& lhs, const Rhs& rhs)
{
    return lhs + rhs;
}

auto a = Add2(3.13, 2.895); // a is a double
auto b = Add2(string{ "Hello" }, string{ " World" }); // b is a std::string

詳細については、「関数テンプレート」をご覧ください

関数のパラメーターと引数

関数にはコンマで区切られた 0 個以上の型のパラメーター リストがあり、それぞれに関数本体内でアクセスする際に使用する名前があります。 関数テンプレートでは、より多くの型または値のパラメーターを指定できます。 呼び出し元は引数を渡します。引数は、パラメーター リストと互換性のある型を持つ具体的な値です。

既定では、引数は関数に値渡しで渡されます。このことは、関数が渡されるオブジェクトのコピーを受け取ることを意味します。 大きなオブジェクトの場合、コピーを作成するとコストが高くなる可能性があり、必ずしも必要であるとは限りません。 引数を参照渡し (具体的には左辺値参照) で渡すようにするには、パラメーターに参照の量指定子を追加します。

void DoSomething(std::string& input){...}

関数が参照によって渡される引数を変更すると、ローカル コピーではなく元のオブジェクトが変更されます。 関数がこのような引数を変更できないようにするには、パラメーターを const> として修飾します。

void DoSomething(const std::string& input){...}

C++11: rvalue-reference または lvalue-reference によって渡される引数を明示的に処理するには、パラメーターで二重アンパサンドを使用してユニバーサル参照を示します。

void DoSomething(const std::string&& input){...}

パラメーターの宣言リストの 1 つのキーワード void で宣言された関数は、キーワード void が引数の宣言リストの最初で唯一のメンバーである限り、引数を受け取りません。 リスト内の他の場所に void 型の引数があるとエラーが発生します。 次に例を示します。

// OK same as GetTickCount()
long GetTickCount( void );

ここで説明する場合を除き、引数を指定 void することは無効ですが、型から派生した型 void (ポインター void や配列など) は、引数宣言リストの任意の void場所に表示される可能性があります。

既定の引数

関数のシグネチャの最後のパラメーターに既定の引数を割り当てることができます。このことは、他の値を指定する必要がない場合は、関数を呼び出すときに呼び出し元が引数を省略できることを意味します。

int DoSomething(int num,
    string str,
    Allocator& alloc = defaultAllocator)
{ ... }

// OK both parameters are at end
int DoSomethingElse(int num,
    string str = string{ "Working" },
    Allocator& alloc = defaultAllocator)
{ ... }

// C2548: 'DoMore': missing default parameter for parameter 2
int DoMore(int num = 5, // Not a trailing parameter!
    string str,
    Allocator& = defaultAllocator)
{...}

詳細については、「既定の引数」をご覧ください。

関数の戻り値の型

関数が別の関数、あるいは組み込み配列を返さない場合があります。ただし、そのような関数であってもこれらの型へのポインター、あるいはラムダを返すことができ、ラムダによって関数オブジェクトが生成されます。 このような場合を除き、関数はスコープ内にある任意の型の値を返すことができます。"値なし" を返す場合、戻り値の型は void です。

後続の戻り値の型

"通常" の戻り値の型は、関数のシグネチャの左側にあります。 末尾の 戻り値の型 は、シグネチャの右端にあり、演算子の -> 前にあります。 後続の戻り値の型は、関数テンプレートにおいて、戻り値の種類がテンプレート パラメーターに依存する場合、特に便利です。

template<typename Lhs, typename Rhs>
auto Add(const Lhs& lhs, const Rhs& rhs) -> decltype(lhs + rhs)
{
    return lhs + rhs;
}

末尾の戻り値の型と組み合わせて使用すると auto 、decltype 式によって生成されるもののプレースホルダーとしてのみ機能し、それ自体は型推論を実行しません。

関数のローカル変数

関数本体内で宣言される変数は、ローカル変数、または単にローカルと呼ばれます。 非静的ローカルは、関数本体内でのみ表示され、スタックで宣言されている場合は、関数が終了したときにスコープ外になります。 ローカル変数を作成して値渡しで値を返す場合、通常、コンパイラで名前付き戻り値の最適化を実行し、不要なコピー操作を回避できます。 ローカル変数を参照渡しで返す場合、呼び出し元がその参照を使用するために実行する試行がローカルの破棄後に発生するため、コンパイラは警告を発行します。

C++ では、ローカル変数を "静的" として宣言することがあります。 変数は関数本体内でのみ認識されますが、関数のすべてのインスタンスに対して変数の 1 つのコピーが存在します。 ローカルな静的オブジェクトは、atexit によって指定された終了時に破棄されます。 プログラムの制御フローがその宣言をバイパスしたために静的オブジェクトが構築されなかった場合、そのオブジェクトを破棄しようとはしません。

戻り値の型の型推論 (C++14)

C++14 では、auto を使用して、後続の戻り値の型を指定しない場合でも、関数本体からの戻り値の型を推論するようにコンパイラに対して指示できます。 auto の推測結果は常に値渡しの戻り値になることに注意してください。 auto&& を使用すると、参照を推測するようにコンパイラに指示できます。

この例で、auto は lhs と rhs の合計の非定数値のコピーとして推測されます。

template<typename Lhs, typename Rhs>
auto Add2(const Lhs& lhs, const Rhs& rhs)
{
    return lhs + rhs; //returns a non-const object by value
}

ただし、 auto deduces 型の const-ness は保持されません。 引数の const 性または ref 性を戻り値が維持する必要がある転送関数の場合、decltype 型の推論規則を使用してすべての型情報を保持する decltype(auto) キーワードを使用できます。 decltype(auto) は、左側の通常の戻り値として、または末尾の戻り値として使用できます。

次の例 (N3493 のコードに基づく) では、テンプレートがインスタンス化されてはじめて戻り値の型が判明する関数の引数を decltype(auto) を使用して完全に転送しています。

template<typename F, typename Tuple = tuple<T...>, int... I>
decltype(auto) apply_(F&& f, Tuple&& args, index_sequence<I...>)
{
    return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...);
}

template<typename F, typename Tuple = tuple<T...>,
    typename Indices = make_index_sequence<tuple_size<Tuple>::value >>
   decltype( auto)
    apply(F&& f, Tuple&& args)
{
    return apply_(std::forward<F>(f), std::forward<Tuple>(args), Indices());
}

関数から複数の値を返す

関数から複数の値を返すさまざまな方法があります。

  1. 名前付きクラスまたは構造体オブジェクト内の値をカプセル化します。 クラスまたは構造体の定義を呼び出し元に表示する必要があります。

    #include <string>
    #include <iostream>
    
    using namespace std;
    
    struct S
    {
        string name;
        int num;
    };
    
    S g()
    {
        string t{ "hello" };
        int u{ 42 };
        return { t, u };
    }
    
    int main()
    {
        S s = g();
        cout << s.name << " " << s.num << endl;
        return 0;
    }
    
  2. std::tuple または std::pair オブジェクトを返します。

    #include <tuple>
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    tuple<int, string, double> f()
    {
        int i{ 108 };
        string s{ "Some text" };
        double d{ .01 };
        return { i,s,d };
    }
    
    int main()
    {
        auto t = f();
        cout << get<0>(t) << " " << get<1>(t) << " " << get<2>(t) << endl;
    
        // --or--
    
        int myval;
        string myname;
        double mydecimal;
        tie(myval, myname, mydecimal) = f();
        cout << myval << " " << myname << " " << mydecimal << endl;
    
        return 0;
    }
    
  3. Visual Studio 2017 バージョン 15.3 以降 (/std:c++17 モード以降で使用可能): 構造化バインディングを使用します。 構造化バインディングの利点は、戻り値を格納する変数が宣言と同時に初期化されることです。場合によっては、大幅に効率的になる場合があります。 ステートメント auto[x, y, z] = f(); では、角かっこによって、関数ブロック全体のスコープ内にある名前が導入され、初期化されます。

    #include <tuple>
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    tuple<int, string, double> f()
    {
        int i{ 108 };
        string s{ "Some text" };
        double d{ .01 };
        return { i,s,d };
    }
    struct S
    {
        string name;
        int num;
    };
    
    S g()
    {
        string t{ "hello" };
        int u{ 42 };
        return { t, u };
    }
    
    int main()
    {
        auto[x, y, z] = f(); // init from tuple
        cout << x << " " << y << " " << z << endl;
    
        auto[a, b] = g(); // init from POD struct
        cout << a << " " << b << endl;
        return 0;
    }
    
  4. 戻り値自体を使用する以外に、関数が呼び出し元が提供するオブジェクトの値を変更または初期化できるように、参照渡しを使用する任意の数のパラメーターを定義することで、値を「返す」ことができます。 詳細については、「Reference-Type Function Arguments (参照型関数の引数)」をご覧ください。

関数ポインター

C++ では、C 言語と同じ方法で、関数ポインターをサポートします。 ただし、通常、よりタイプ セーフな代替手段で関数オブジェクトが使用されます。

関数ポインター型を返す関数を宣言する場合は、関数ポインター型のエイリアスを宣言するために使用 typedef することをお勧めします。 次に例を示します。

typedef int (*fp)(int);
fp myFunction(char* s); // function returning function pointer

これが行われていない場合、次のように、識別子 (fp 上記の例) を関数名と引数リストに置き換えることで、関数ポインターの宣言子構文から関数宣言の適切な構文が推定される可能性があります。

int (*myFunction(char* s))(int);

上記の宣言は、先ほど使用 typedef した宣言と同じです。

関連項目

関数のオーバーロード
可変個の引数リストを取る関数
明示的に既定された関数および削除された関数
関数の引数依存名の参照 (Koenig 参照)
既定の引数
インライン関数