| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
Tags
- Lock-free Stack
- LCA
- Github
- map
- 트라이
- PROJECT
- Strongly Connected Component
- Express.js
- JavaScript
- 이분 탐색
- ccw 알고리즘
- 게임 서버 아키텍처
- 그래프 탐색
- R 그래프
- 2-SAT
- Binary Lifting
- Behavior Design Pattern
- 비트마스킹
- 자바스크립트
- 비트필드를 이용한 dp
- Prisma
- 최소 공통 조상
- DP
- Spin Lock
- trie
- 강한 연결 요소
- 벨만-포드
- 분리 집합
- SCC
- localstorage
Archives
- Today
- Total
dh_0e
[C++] 콜백(Callback) 함수, using & typedef 본문
콜백(Callback) 함수
- 정의: 나중에 호출될 함수를 의미하며, 함수를 실행할 때, 인자로 다른 함수를 전달하여 특정 이벤트가 발생하거나 작업이 완료되었을 때 그 함수가 실행되도록 하는 기법
- 특징: 함수가 실행 주도권을 갖는 것이 아니라, 특정 시점에 호출되도록 등록하는 방식
- 용도: 이벤트 처리, 서버 통신 완료 후 작업, 정렬 알고리즘의 비교 로직 등
- 함수 포인터를 직접 쓰는 방식과 Modern C++의 std::function을 사용하는 방법이 있음
1. 전통적인 함수 포인터 방식 (typedef, using)
- C 스타일의 방식으로, 함수가 저장된 메모리 주소를 직접 가리켜 전달함
- 문법은 복잡하지만 추가적인 오버헤드가 거의 없음
// 문법: typedef 반환형 (*별칭이름)(매개변수);
typedef void (*CallbackPtr)(int, double);
// 문법: using 별칭이름 = 반환형 (*)(매개변수);
using CallbackPtr = void (*)(int, double);
- 반환형: void
- 별칭 이름: CallbackPtr (반드시 *와 함께 괄호로 묶어야 함)
- 매개변수: (int, double)
#include <iostream>
// 1. 선언
using ModernAlias = void (*)(int);
// 2. 콜백을 사용하는 함수
void RunLogic(int value, ModernAlias f) {
std::cout << "로직 실행 중..." << std::endl;
if (f) f(value); // 함수 주소를 찾아가 호출
}
// 3. 실제 전달할 함수
void OnFinished(int res) {
std::cout << "전통적 방식 결과: " << res << std::endl;
}
int main() {
RunLogic(100, OnFinished); // 함수 이름을 인자로 전달
return 0;
}
2. 모던 C++ 방식 (std::function)
- <functional> 헤더를 사용하는 방식으로 함수 포인터뿐만 아니라 람다, 함수 객체(Functor) 등 호출 가능한 모든 것을 담을 수 있음
- 함수 포인터 특유의 (*) 기호가 사라져서 훨씬 깔끔하며 수학 함수 기호 $f(x)$와 비슷해서 이해하기 쉬움
#include <functional>
// 문법: using 별칭이름 = std::function<반환형(매개변수)>;
using CallbackPtr = std::function<void(int, double)>;
- 형태: using 함수이름 = std::function<return type(parameters)>
일반 함수, 람다 전달
#include <iostream>
#include <functional>
// 문법: using 별칭 = std::function<반환형(매개변수)>;
using ModernCallback = std::function<void(int, double)>;
void ExecuteModern(ModernCallback cb) {
if (cb) cb(20, 6.28);
}
// 실제 콜백으로 쓰일 '일반 함수' 정의
void MyCallback(int a, double b) {
std::cout << "일반 함수 콜백 호출됨! 값: " << a << ", " << b << std::endl;
}
int main() {
// 1. 일반 함수 전달 가능
ExecuteModern(MyCallback);
// 2. 람다 전달 가능 (함수 포인터와의 결정적 차이!)
ExecuteModern([](int a, double b) {
std::cout << "모던 방식(람다): " << a << "와 " << b << std::endl;
});
return 0;
}
함수 객체(Functor) 전달
#include <iostream>
#include <functional>
// [Step 1] Functor 정의
class UpgradeTracker {
private:
int count = 0; // 상태(강화 시도 횟수)를 저장할 수 있음!
public:
// operator()를 오버로딩하면 객체를 함수처럼 쓸 수 있음
void operator()(bool success, std::string name) {
count++;
std::cout << "[" << name << "] 현재까지 총 " << count << "번 시도 중..." << std::endl;
if (success) std::cout << ">>> 강화 성공!" << std::endl;
}
};
// [Step 2] 콜백을 받는 함수
void AttemptUpgrade(std::string item, std::function<void(bool, std::string)> callback) {
bool result = (rand() % 2 == 0);
callback(result, item); // 객체지만 함수처럼 호출됨
}
int main() {
UpgradeTracker myTracker; // 객체 생성
// [Step 3] 함수 객체를 콜백으로 전달
AttemptUpgrade("집행검", myTracker);
AttemptUpgrade("집행검", myTracker);
return 0;
}
Thread 예시
void ThreadManager::Launch(function<void(void)> callback)
{
LockGuard guard(_lock);
_threads.push_back(thread([=]()
{
InitTLS();
callback();
DestroyTLS();
}));
}
- 다음과 같이 람다 함수를 사용하여 스레드가 callback 함수를 실행하기 전후로 다른 함수를 실행하게끔 해줄 수도 있음
| 구분 | 함수 포인터 (*) | std::function |
| 선언 문법 | void (*)(int, double) | std::function<void(int, double)> |
| 람다 캡처 | 불가능 (상태를 가질 수 없음) | 가능 (외부 변수를 가져와 쓸 수 있음) |
| 성능 | 매우 빠름 (오버헤드 없음) | 약간 느림 (객체 생성 및 호출 비용 발생) |
| 유연성 | 낮음 (일반 함수만 가능) | 높음 (함수 객체, 람다 등 모두 가능) |
typedef & using
- 두 키워드 모두 복잡한 타입에 별칭(Alias)을 붙여주는 역할을 하지만, 기능과 직관성에서 큰 차이가 있음
- using은 typedef가 할 수 있는 모든 것을 할 수 있으며, 그 이상의 기능을 제공함
typedef (Type Definition)
- 정의: C언어 시절부터 사용된 전통적인 타입 정의 방식
- 특징: 변수 선언문 앞에 typedef를 붙여서 만드며, 이름이 문장 중간에 끼어 있어 가독성이 떨어지는 경우가 많음
// 문법: typedef 반환형 (*별칭이름)(매개변수);
typedef void (*CallbackPtr)(int, double);
void MyFunc(int a, double b) { /* 로직 */ }
int main() {
CallbackPtr cb = MyFunc; // 별칭을 타입처럼 사용
cb(10, 3.14);
return 0;
}
using (Type Alias)
- 정의: C++11부터 도입된 현대적인 타입 별칭 방식
- 특징: using 별칭 = 타입; 형태를 취하며 대입 연산자(=)를 사용해 가독성이 좋음
// 문법: using 별칭이름 = 반환형 (*)(매개변수);
using CallbackPtr = void (*)(int, double);
void MyFunc(int a, double b) { /* 로직 */ }
int main() {
CallbackPtr cb = MyFunc; // 변수 선언하듯 사용
cb(20, 6.28);
return 0;
}
typedef vs using
- typedef와 using의 가장 큰 차이는 템플릿 별칭을 만들 수 있는가임
- using은 템플릿을 사용하여 새로운 타입을 정의할 수 있음
- typedef는 직접적으로 템플릿화할 수 없어, 구조체 안에 넣어서 꺼내 쓰는 편법이 필요함
/*---using---*/
template <typename T>
using Point = std::pair<T, T>; // T 타입의 좌표 쌍 정의
Point<int> p1; // 간단하게 사용 가능
Point<double> p2;
/*---typedef---*/
template <typename T>
struct PointWrapper {
typedef std::pair<T, T> type;
};
PointWrapper<int>::type p1; // 사용 시마다 ::type을 붙여야 함 (번거로움)'C++' 카테고리의 다른 글
| [C++] Smart Pointer (0) | 2026.02.09 |
|---|---|
| [C++] lambda, 타입 추론(auto), 일반화 함수(template), Singleton in C++ (3) | 2025.08.05 |
| [C++] memset, isupper/islower (0) | 2025.02.13 |
| [C++] stringstream (2) | 2024.07.22 |
| [C++] max_element, min_element, find (0) | 2024.07.12 |