C++ Default Arguments | 'Default Arguments' Guide

C++ Default Arguments | 'Default Arguments' Guide

이 글의 핵심

Default arguments allow optional parameters in a single function signature. Organizes declaration/redeclaration rules, pitfalls with overloading/virtual functions, and API design patterns.

What are Default Arguments?

Default arguments allow specifying default values for function parameters, enabling argument omission during calls. Can replace function overloading to make code more concise.

Below is an implementation example using C++. Try running the code directly to check its operation.

void greet(const std::string& name = "Guest") {
    std::cout << "Hello, " << name << "!" << std::endl;
}

int main() {
    greet("Alice");  // Hello, Alice!
    greet();         // Hello, Guest!
}

Why needed?:

  • Conciseness: Implement optional parameters without overloading
  • Backward compatibility: Maintain existing code when adding new parameters
  • Readability: Set frequently used values as defaults

Below is an implementation example using C++. Understand the role of each part while examining the code.

// ❌ Overloading: Code duplication
void connect(const std::string& host) {
    connect(host, 8080);
}

void connect(const std::string& host, int port) {
    // Connection logic
}

// ✅ Default argument: Concise
void connect(const std::string& host, int port = 8080) {
    // Connection logic
}

Basic Usage

Here is detailed implementation code using C++. Understand the role of each part while examining the code.

// Single default argument
void func(int x = 10) {
    std::cout << x << std::endl;
}

// Multiple default arguments
void func(int x = 10, int y = 20, int z = 30) {
    std::cout << x << ", " << y << ", " << z << std::endl;
}

int main() {
    func();           // 10, 20, 30
    func(1);          // 1, 20, 30
    func(1, 2);       // 1, 2, 30
    func(1, 2, 3);    // 1, 2, 3
}

Rules

Below is an implementation example using C++. Understand the role of each part while examining the code.

// ✅ Defaults from right
void func(int x, int y = 20, int z = 30) {
    // OK
}

// ❌ Gap in middle
// void func(int x, int y = 20, int z) {  // Error
//     // ...
// }

// ✅ All defaults
void func(int x = 10, int y = 20, int z = 30) {
    // OK
}

Default Argument Rules in Detail:

  1. Continuous from right: Parameters with default values must be continuous from right.

Below is an implementation example using C++. Try running the code directly to check its operation.

// ✅ Correct examples
void f1(int a, int b = 2, int c = 3);
void f2(int a = 1, int b = 2, int c = 3);

// ❌ Incorrect examples
// void f3(int a = 1, int b, int c = 3);  // Error: b has no default in middle
// void f4(int a = 1, int b, int c);      // Error: only a has default
  1. Defaults only in declaration: Specify default values only in header file declaration, omit in implementation file.

Below is an implementation example using C++. Try running the code directly to check its operation.

// header.h
void func(int x, int y = 20);

// source.cpp
void func(int x, int y) {  // No default value
    std::cout << x << ", " << y << '\n';
}
  1. Can add defaults in redeclaration: Can add default values in redeclaration for same parameter, but duplicate specification not allowed.

Below is an implementation example using C++. Understand the role of each part while examining the code.

void func(int x, int y, int z);  // Declaration 1

void func(int x, int y, int z = 30);  // Declaration 2: Add default to z

void func(int x, int y = 20, int z);  // Declaration 3: Add default to y

// Final: func(int x, int y = 20, int z = 30)

// ❌ Duplicate specification
// void func(int x, int y = 20, int z = 30);  // Error: y, z duplicate

Constraints and Pitfalls with Virtual Functions

Default arguments are not virtual. Even in virtual function calls, which default value is used is determined at compile-time by the static type used in the call.

Below is an implementation example using C++. Define a class to encapsulate data and functionality. Understand the role of each part while examining the code.

// Type definition
struct Base {
    virtual void f(int x = 1) { std::cout << "B " << x << '\n'; }
};
struct Derived : Base {
    void f(int x = 2) override { std::cout << "D " << x << '\n'; }
};

int main() {
    Derived d;
    Base& b = d;
    d.f();   // D 2  — Static type Derived → Derived's default
    b.f();   // D 1  — Static type Base → Base's default (not derived's!)
}

Having different default values in override function in derived class is syntactically possible, but only confuses users calling through base pointer. In public API, safer to put default values only in base declaration and omit in derived, or handle defaults with non-virtual wrapper.

Also, when overriding virtual function with default arguments, signature (name·parameter list) must match, so default value list must align with header design separate from inheritance rules.

Practical Examples

Example 1: Log Function

Here is detailed implementation code using C++. Import the necessary modules and perform branching with conditionals. Understand the role of each part while examining the code.

#include <iostream>
#include <fstream>
#include <string>

enum class LogLevel {
    INFO,
    WARNING,
    ERROR
};

void log(const std::string& message, 
         LogLevel level = LogLevel::INFO,
         const std::string& file = "log.txt") {
    std::ofstream ofs(file, std::ios::app);
    
    switch (level) {
        case LogLevel::INFO:
            ofs << "[INFO] ";
            break;
        case LogLevel::WARNING:
            ofs << "[WARNING] ";
            break;
        case LogLevel::ERROR:
            ofs << "[ERROR] ";
            break;
    }
    
    ofs << message << '\n';
}

int main() {
    log("Application started");  // Default: INFO, log.txt
    log("Low memory", LogLevel::WARNING);  // WARNING, log.txt
    log("Crash", LogLevel::ERROR, "error.txt");  // ERROR, error.txt
}

Summary

Key Points

  1. Default arguments: Specify default values for parameters
  2. Rules: Continuous from right, declaration only
  3. Virtual functions: Defaults determined by static type
  4. Overloading: Can cause ambiguity
  5. API design: Use for 2-3 parameters max

When to Use

Use default arguments when:

  • Need optional parameters
  • Want to maintain backward compatibility
  • Have frequently used default values
  • Simplifying overload set

Don’t use when:

  • Too many parameters (use options struct)
  • Virtual function with different defaults
  • Causes ambiguity with overloads
  • Expression has side effects

Best Practices

  • ✅ Put defaults in header declaration
  • ✅ Keep defaults simple and constant
  • ✅ Limit to 2-3 default parameters
  • ❌ Don’t use different defaults in virtual override
  • ❌ Don’t use expressions with side effects

Master default arguments for cleaner function APIs! 🚀