[2026] C++ std::function vs Function Pointers: Flexibility vs Speed

[2026] C++ std::function vs Function Pointers: Flexibility vs Speed

이 글의 핵심

std::function vs raw function pointers: pointers are faster and smaller; std::function type-erases lambdas with captures and functors. Callback design, SBO, and when to template instead.

For encapsulating requests as callable objects (undo queues, jobs), the Command pattern builds on the same callback ideas.

Introduction: “How should I store callbacks?”

Function pointers are small and fast but cannot carry capturing lambdas. std::function is flexible but has overhead. This article covers:

  • Capabilities
  • Benchmark trends
  • Design patterns

Comparison

AspectFunction pointerstd::function
Capturing lambdas❌ No✅ Yes
Functors❌ No✅ Yes
Size8 bytes (pointer)32+ bytes (SBO + vtable)
Heap allocationNeverSometimes (large captures)
SpeedFastestSlower (type erasure)
C interop✅ Yes❌ No

Function pointers

Basic usage

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

int add(int a, int b) {
    return a + b;
}
// Function pointer type
int (*funcPtr)(int, int) = add;
// Or with typedef
typedef int (*BinaryOp)(int, int);
BinaryOp op = add;
// Call
int result = funcPtr(10, 20);  // 30

Limitations

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

int x = 5;
auto lambda = [x](int y) { return x + y; };  // Capturing lambda
// ❌ Error: cannot convert capturing lambda to function pointer
int (*ptr)(int) = lambda;
// ✅ Only non-capturing lambdas work
auto lambda2 = [](int y) { return y * 2; };
int (*ptr2)(int) = lambda2;  // OK

std::function

Basic usage

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

#include <functional>
std::function<int(int, int)> func = [](int a, int b) {
    return a + b;
};
int result = func(10, 20);  // 30

Storing capturing lambdas

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

int multiplier = 5;
std::function<int(int)> func = [multiplier](int x) {
    return x * multiplier;  // ✅ OK: captures multiplier
};
int result = func(10);  // 50

Storing functors

아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

struct Adder {
    int base;
    
    int operator()(int x) const {
        return x + base;
    }
};
std::function<int(int)> func = Adder{10};
int result = func(5);  // 15

Performance benchmarks

Test setup: GCC 13, -O3, 10M calls

Callable typeTime (ms)Overhead vs direct
Direct call81.0×
Function pointer121.5×
std::function (no capture)354.4×
std::function (small capture)384.8×
std::function (large capture)425.3×
Template parameter81.0×
Key insight: Templates with auto or type parameters have zero overhead compared to direct calls.

Small Buffer Optimization (SBO)

std::function uses SBO to avoid heap allocation for small captures: 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <functional>
#include <iostream>
struct Small {
    int x;  // 4 bytes
};
struct Large {
    char data[100];  // 100 bytes
};
int main() {
    // Small: likely uses SBO (no heap allocation)
    std::function<void()> f1 = [s = Small{42}]() {
        std::cout << s.x << "\n";
    };
    
    // Large: likely heap allocation
    std::function<void()> f2 = [l = Large{}]() {
        std::cout << "Large\n";
    };
}

Typical SBO size: 16-32 bytes (implementation-dependent)

Real-world use cases

1. Event system

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <functional>
#include <vector>
#include <string>
class EventSystem {
    using Callback = std::function<void(const std::string&)>;
    std::vector<Callback> listeners_;
    
public:
    void subscribe(Callback cb) {
        listeners_.push_back(std::move(cb));
    }
    
    void notify(const std::string& event) {
        for (auto& cb : listeners_) {
            cb(event);
        }
    }
};
// Usage
EventSystem events;
int counter = 0;
events.subscribe([&counter](const std::string& e) {
    ++counter;  // ✅ Capturing lambda works
    std::cout << "Event: " << e << "\n";
});
events.notify("user_login");

2. Command pattern with undo

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <functional>
#include <stack>
class CommandManager {
    std::stack<std::function<void()>> undoStack_;
    
public:
    void execute(std::function<void()> action, 
                 std::function<void()> undo) {
        action();
        undoStack_.push(std::move(undo));
    }
    
    void undo() {
        if (!undoStack_.empty()) {
            undoStack_.top()();
            undoStack_.pop();
        }
    }
};
// Usage
CommandManager mgr;
int value = 10;
mgr.execute(
    [&value]() { value += 5; },  // Do
    [&value]() { value -= 5; }   // Undo
);

3. Strategy pattern

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <functional>
#include <string>
class Validator {
    std::function<bool(const std::string&)> strategy_;
    
public:
    void setStrategy(std::function<bool(const std::string&)> s) {
        strategy_ = std::move(s);
    }
    
    bool validate(const std::string& input) {
        return strategy_ ? strategy_(input) : true;
    }
};
// Usage
Validator validator;
// Email validation
validator.setStrategy([](const std::string& s) {
    return s.find('@') != std::string::npos;
});
bool valid = validator.validate("test@example.com");  // true

When to use templates instead

Template callback (zero overhead)

아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

template<typename Func>
void process(const std::vector<int>& data, Func callback) {
    for (int value : data) {
        callback(value);  // Inlined, no indirection
    }
}
// Usage
process(data, [](int x) { std::cout << x << "\n"; });

Benchmark (1M elements):

  • Template version: 45ms
  • std::function version: 180ms Trade-off: Templates increase code size (one instantiation per callable type).

Common mistakes

Mistake 1: Empty std::function

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

std::function<void()> func;
// ❌ Throws std::bad_function_call
func();
// ✅ Check first
if (func) {
    func();
}

Mistake 2: Dangling captures

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

std::function<int()> createCallback() {
    int local = 42;
    return [&local]() { return local; };  // ❌ Dangling reference!
}
// ✅ Capture by value
std::function<int()> createCallback() {
    int local = 42;
    return [local]() { return local; };
}

Mistake 3: Assigning incompatible signature

아래 코드는 cpp를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

std::function<int(int)> func;
// ❌ Error: signature mismatch
func = [](int a, int b) { return a + b; };
// ✅ Correct signature
func = [](int a) { return a * 2; };

Mistake 4: Unnecessary std::function

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ Overhead for simple case
void process(std::function<int(int)> func, int x) {
    return func(x);
}
// ✅ Template for zero overhead
template<typename Func>
auto process(Func func, int x) {
    return func(x);
}

Advanced: Type erasure internals

Simplified std::function implementation

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

template<typename Signature>
class SimpleFunction;
template<typename R, typename....Args>
class SimpleFunction<R(Args...)> {
    struct Concept {
        virtual R call(Args...) = 0;
        virtual ~Concept() = default;
    };
    
    template<typename F>
    struct Model : Concept {
        F func_;
        Model(F f) : func_(std::move(f)) {}
        R call(Args....args) override {
            return func_(std::forward<Args>(args)...);
        }
    };
    
    std::unique_ptr<Concept> ptr_;
    
public:
    template<typename F>
    SimpleFunction(F f) 
        : ptr_(std::make_unique<Model<F>>(std::move(f))) {}
    
    R operator()(Args....args) {
        return ptr_->call(std::forward<Args>(args)...);
    }
};

Compiler support

CompilerFunction pointersstd::function
GCCAll versions4.5+ (C++11)
ClangAll versions3.1+
MSVCAll versions2010+

Keywords

std::function, function pointer, callback, type erasure, lambda, C++11, performance, SBO

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3