본문으로 건너뛰기
Previous
Next
C++ std::function vs Function Pointers: Flexibility vs Speed

C++ std::function vs Function Pointers: Flexibility vs Speed

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](/en/blog/cpp-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

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

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

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

Storing capturing lambdas

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

Storing functors

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:

#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

#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

#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

#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("[email protected]");  // true

When to use templates instead

Template callback (zero overhead)

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

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

Mistake 2: Dangling captures

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

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

// ❌ 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

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+

  • [C++ Lambda complete guide](/en/blog/cpp-lambda-complete/
  • [Command pattern](/en/blog/cpp-command-pattern/
  • Performance optimization

Keywords

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


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. std::function vs raw function pointers: pointers are faster and smaller; std::function type-erases lambdas with captures… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • [C++ Command Pattern: Complete Guide | Undo· Redo](/en/blog/cpp-command-pattern/
  • [C++ emplace vs push: Performance, Move Semantics, and](/en/blog/cpp-comparison-11-emplace-push/
  • [C++ Lambdas: Syntax, Captures, mutable, and Generic Lambdas](/en/blog/cpp-lambda-complete/

이 글에서 다루는 키워드 (관련 검색어)

C++, std::function, function pointer, performance, lambda, callback, C++11 등으로 검색하시면 이 글이 도움이 됩니다.