[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
| Aspect | Function pointer | std::function |
|---|---|---|
| Capturing lambdas | ❌ No | ✅ Yes |
| Functors | ❌ No | ✅ Yes |
| Size | 8 bytes (pointer) | 32+ bytes (SBO + vtable) |
| Heap allocation | Never | Sometimes (large captures) |
| Speed | Fastest | Slower (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 type | Time (ms) | Overhead vs direct |
|---|---|---|
| Direct call | 8 | 1.0× |
| Function pointer | 12 | 1.5× |
| std::function (no capture) | 35 | 4.4× |
| std::function (small capture) | 38 | 4.8× |
| std::function (large capture) | 42 | 5.3× |
| Template parameter | 8 | 1.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::functionversion: 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
| Compiler | Function pointers | std::function |
|---|---|---|
| GCC | All versions | 4.5+ (C++11) |
| Clang | All versions | 3.1+ |
| MSVC | All versions | 2010+ |
Related posts
Keywords
std::function, function pointer, callback, type erasure, lambda, C++11, performance, SBO