デフォルト引数

関数呼び出しを柔軟に。引数を省略可能にする方法

概要

デフォルト引数により、 関数の引数にデフォルト値を設定できます。

呼び出し側で引数を省略した場合、 デフォルト値が使われます。

これにより、関数の呼び出しが より柔軟で使いやすくなります。

基本形

戻り値の型 関数名(型 引数1 = デフォルト値1, 型 引数2 = デフォルト値2) {
    // 処理
}

#include <iostream>

void greet(std::string name = "Guest") {
    std::cout << "こんにちは, " << name << "!" << std::endl;
}

int main() {
    greet();              // "こんにちは, Guest!"
    greet("太郎");        // "こんにちは, 太郎!"
    
    return 0;
}

こんにちは, Guest!
こんにちは, 太郎!

name引数は省略可能。 省略された場合、"Guest" が使われます。

複数のデフォルト引数

例:矩形の描画

#include <iostream>

void drawRectangle(int width, int height, char border = '*') {
    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j++) {
            std::cout << border;
        }
        std::cout << std::endl;
    }
}

int main() {
    drawRectangle(5, 3);           // *で描画
    std::cout << std::endl;
    drawRectangle(5, 3, '#');      // #で描画
    
    return 0;
}

*****
*****
*****

#####
#####
#####

重要なルール

ルール1: デフォルト引数は右から順に

デフォルト値を持つ引数は、 左側のすべてがデフォルト値を持つ必要はありません。 しかし、実務では右側に集めるのが慣例です。

// 良い例
void func(int a, int b = 10, int c = 20) { }

// 悪い例(避けるべき)
void func(int a = 5, int b, int c) { }  // エラー

ルール2: 宣言と定義での指定

デフォルト値は宣言(プロトタイプ)時に指定します。 定義時に重複して指定するとエラーです。

// 正しい
int add(int a, int b = 0);  // 宣言時にデフォルト値を指定

int add(int a, int b) {     // 定義時には指定しない
    return a + b;
}

// 誤り
int multiply(int a, int b = 1);  // 宣言
int multiply(int a, int b = 1) { // エラー!: 重複
    return a * b;
}

実践例

例1: ファイル出力関数

#include <iostream>

void writeLine(const std::string & text, int times = 1, char separator = '\n') {
    for (int i = 0; i < times; i++) {
        std::cout << text;
        if (i < times - 1) {
            std::cout << separator;
        }
    }
    std::cout << std::endl;
}

int main() {
    writeLine("Hello");                    // 1回出力
    writeLine("World", 3);                 // 3回、改行区切り
    writeLine("Test", 4, ' ');             // 4回、スペース区切り
    
    return 0;
}

Hello
World
World
World
Test Test Test Test

例2: 支払い計算機

#include <iostream>
#include <iomanip>

double calculateTotal(double price, int quantity = 1, double taxRate = 0.1) {
    return price * quantity * (1.0 + taxRate);
}

int main() {
    std::cout << std::fixed << std::setprecision(2);
    
    std::cout << "単価100円、1個: " << calculateTotal(100) << std::endl;
    std::cout << "単価100円、3個: " << calculateTotal(100, 3) << std::endl;
    std::cout << "単価100円、3個、税率0: " << calculateTotal(100, 3, 0.0) << std::endl;
    
    return 0;
}

単価100円、1個: 110.00
単価100円、3個: 330.00
単価100円、3個、税率0: 300.00

オーバーロード vs デフォルト引数

同じことがオーバーロードでもできますが、 デフォルト引数の方がシンプルな場合が多いです。

比較

// デフォルト引数を使った方法
void display(int x = 0, int y = 0) {
    std::cout << "(" << x << ", " << y << ")" << std::endl;
}

// オーバーロードを使った方法
void display() {
    display(0, 0);
}

void display(int x) {
    display(x, 0);
}

void display(int x, int y) {
    std::cout << "(" << x << ", " << y << ")" << std::endl;
}

デフォルト引数を使った方がよりシンプルですね。 ただし、処理が大きく異なる場合はオーバーロードが適切です。

デフォルト値の注意点

例:デフォルト値は関数呼び出し時に評価

#include <iostream>

int getValue() {
    static int counter = 0;
    return ++counter;
}

void display(int x = getValue()) {
    std::cout << x << std::endl;
}

int main() {
    display();   // getValue() が呼ばれて 1
    display();   // getValue() が呼ばれて 2
    display(10); // getValue() は呼ばれない(明示的に10を渡す)
    
    return 0;
}

1
2
10

ポイント

  • デフォルト引数で関数呼び出しを柔軟に
  • デフォルト値は宣言時に指定
  • デフォルト引数は右側に集める
  • オーバーロードと組み合わせて使うことも可能
  • デフォルト値の評価は呼び出し時

よくある誤り

誤り1: デフォルト引数を左側に置く

void func(int a = 10, int b) { }  // エラー!
// 呼び出す時に func(5) とした場合、
// 5 が a に行くのか b に行くのか不明確

誤り2: 定義時にもデフォルト値を指定

void func(int x = 5);  // 宣言

void func(int x = 5) { // エラー!: 重複
    // ...
}

誤り3: 避けるべき使い方

// グローバル変数をデフォルト値として使う(避けるべき)
int globalValue = 0;

void process(int x = globalValue) {
    std::cout << x << std::endl;
}

やってみよう

練習1: 計算機関数に適切なデフォルト値を追加。

練習2: 出力フォーマット関数(フォーマット、幅、精度のデフォルト)。

練習3: ユーザー認証関数(デフォルト役割は"user")。

チャレンジ: クラスのメンバー関数にデフォルト引数を追加。

まとめ

  • デフォルト引数で関数呼び出しを簡潔に
  • 宣言時に指定し、定義時に重複させない
  • デフォルト引数 vs オーバーロード:状況に応じて選択
  • 関数のインターフェース設計に重要な要素